1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
23 * Terminal Page/Line/Cell/Char Handling
24 * This file implements page handling of a terminal. It is split into pages,
25 * lines, cells and characters. Each object is independent of the next upper
28 * The Terminal layer keeps each line of a terminal separate and dynamically
29 * allocated. This allows us to move lines from main-screen to history-buffers
30 * very fast. Same is true for scrolling, top/bottom borders and other buffer
33 * While lines are dynamically allocated, cells are not. This would be a waste
34 * of memory and causes heavy fragmentation. Furthermore, cells are moved much
35 * less frequently than lines so the performance-penalty is pretty small.
36 * However, to support combining-characters, we have to initialize and cleanup
37 * cells properly and cannot just release the underlying memory. Therefore,
38 * cells are treated as proper objects despite being allocated in arrays.
40 * Each cell has a set of attributes and a stored character. This is usually a
41 * single Unicode character stored as 32bit UCS-4 char. However, we need to
42 * support Unicode combining-characters, therefore this gets more complicated.
43 * Characters themselves are represented by a "term_char_t" object. It
44 * should be treated as a normal integer and passed by value. The
45 * sorrounding struct is just to hide the internals. A term-char can contain a
46 * base character together with up to 2 combining-chars in a single integer.
47 * Only if you need more combining-chars (very unlikely!) a term-char is a
48 * pointer to an allocated storage. This requires you to always free term-char
49 * objects once no longer used (even though this is a no-op most of the time).
50 * Furthermore, term-char objects are not ref-counted so you must duplicate them
51 * in case you want to store it somewhere and retain a copy yourself. By
52 * convention, all functions that take a term-char object will not duplicate
53 * it but implicitly take ownership of the passed value. It's up to the caller
54 * to duplicate it beforehand, in case it wants to retain a copy.
56 * If it turns out, that more than 2 comb-chars become common in specific
57 * languages, we can try to optimize this. One idea is to ref-count allocated
58 * characters and store them in a hash-table (like gnome's libvte3 does). This
59 * way we will never have two allocated chars for the same content. Or we can
60 * simply put two uint64_t into a "term_char_t". This will slow down operations
61 * on systems that don't need that many comb-chars, but avoid the dynamic
62 * allocations on others.
63 * Anyhow, until we have proper benchmarks, we will keep the current code. It
64 * seems to compete very well with other solutions so far.
66 * The page-layer is a one-dimensional array of lines. Considering that each
67 * line is a one-dimensional array of cells, the page layer provides the
68 * two-dimensional cell-page required for terminals. The page itself only
69 * operates on lines. All cell-related operations are forwarded to the correct
71 * A page does not contain any cursor tracking. It only provides the raw
72 * operations to shuffle lines and modify the page.
80 #include "term-internal.h"
83 /* maximum UCS-4 character */
84 #define CHAR_UCS4_MAX (0x10ffff)
85 /* mask for valid UCS-4 characters (21bit) */
86 #define CHAR_UCS4_MASK (0x1fffff)
87 /* UCS-4 replacement character */
88 #define CHAR_UCS4_REPLACEMENT (0xfffd)
90 /* real storage behind "term_char_t" in case it's not packed */
91 typedef struct term_character {
93 uint32_t codepoints[];
97 * char_pack() takes 3 UCS-4 values and packs them into a term_char_t object.
98 * Note that UCS-4 chars only take 21 bits, so we still have the LSB as marker.
99 * We set it to 1 so others can distinguish it from pointers.
101 static inline term_char_t char_pack(uint32_t v1, uint32_t v2, uint32_t v3) {
102 uint64_t packed, u1, u2, u3;
109 packed |= (u1 & (uint64_t)CHAR_UCS4_MASK) << 43;
110 packed |= (u2 & (uint64_t)CHAR_UCS4_MASK) << 22;
111 packed |= (u3 & (uint64_t)CHAR_UCS4_MASK) << 1;
113 return TERM_CHAR_INIT(packed);
116 #define char_pack1(_v1) char_pack2((_v1), CHAR_UCS4_MAX + 1)
117 #define char_pack2(_v1, _v2) char_pack3((_v1), (_v2), CHAR_UCS4_MAX + 1)
118 #define char_pack3(_v1, _v2, _v3) char_pack((_v1), (_v2), (_v3))
121 * char_unpack() is the inverse of char_pack(). It extracts the 3 stored UCS-4
122 * characters and returns them. Note that this does not validate the passed
123 * term_char_t. That's the responsibility of the caller.
124 * This returns the number of characters actually packed. This obviously is a
125 * number between 0 and 3 (inclusive).
127 static inline uint8_t char_unpack(term_char_t packed, uint32_t *out_v1, uint32_t *out_v2, uint32_t *out_v3) {
130 v1 = (packed._value >> 43) & (uint64_t)CHAR_UCS4_MASK;
131 v2 = (packed._value >> 22) & (uint64_t)CHAR_UCS4_MASK;
132 v3 = (packed._value >> 1) & (uint64_t)CHAR_UCS4_MASK;
141 return (v1 > CHAR_UCS4_MAX) ? 0 :
142 ((v2 > CHAR_UCS4_MAX) ? 1 :
143 ((v3 > CHAR_UCS4_MAX) ? 2 :
147 /* cast a term_char_t to a term_character* */
148 static inline term_character *char_to_ptr(term_char_t ch) {
149 return (term_character*)(unsigned long)ch._value;
152 /* cast a term_character* to a term_char_t */
153 static inline term_char_t char_from_ptr(term_character *c) {
154 return TERM_CHAR_INIT((unsigned long)c);
158 * char_alloc() allocates a properly aligned term_character object and returns
159 * a pointer to it. NULL is returned on allocation errors. The object will have
160 * enough room for @n following UCS-4 chars.
161 * Note that we allocate (n+1) characters and set the last one to 0 in case
162 * anyone prints this string for debugging.
164 static term_character *char_alloc(uint8_t n) {
168 r = posix_memalign((void**)&c,
169 MAX(sizeof(void*), (size_t)2),
170 sizeof(*c) + sizeof(*c->codepoints) * (n + 1));
175 c->codepoints[n] = 0;
181 * char_free() frees the memory allocated via char_alloc(). It is safe to call
182 * this on any term_char_t, only allocated characters are freed.
184 static inline void char_free(term_char_t ch) {
185 if (term_char_is_allocated(ch))
186 free(char_to_ptr(ch));
190 * This appends @append_ucs4 to the existing character @base and returns
191 * it as a new character. In case that's not possible, @base is returned. The
192 * caller can use term_char_same() to test whether the returned character was
193 * freshly allocated or not.
195 static term_char_t char_build(term_char_t base, uint32_t append_ucs4) {
196 /* soft-limit for combining-chars; hard-limit is currently 255 */
197 const size_t climit = 64;
202 /* ignore invalid UCS-4 */
203 if (append_ucs4 > CHAR_UCS4_MAX)
206 if (term_char_is_null(base)) {
207 return char_pack1(append_ucs4);
208 } else if (!term_char_is_allocated(base)) {
209 /* unpack and try extending the packed character */
210 n = char_unpack(base, &buf[0], &buf[1], &buf[2]);
214 return char_pack1(append_ucs4);
219 return char_pack2(buf[0], append_ucs4);
224 return char_pack3(buf[0], buf[1], append_ucs4);
230 /* already fully packed, we need to allocate a new one */
233 /* already an allocated type, we need to allocate a new one */
234 c = char_to_ptr(base);
239 /* bail out if soft-limit is reached */
243 /* allocate new char */
244 c = char_alloc(n + 1);
248 memcpy(c->codepoints, t, sizeof(*t) * n);
249 c->codepoints[n] = append_ucs4;
251 return char_from_ptr(c);
255 * term_char_set() - Reset character to a single UCS-4 character
256 * @previous: term-char to reset
257 * @append_ucs4: UCS-4 char to set
259 * This frees all resources in @previous and re-initializes it to @append_ucs4.
260 * The new char is returned.
262 * Usually, this is used like this:
263 * obj->ch = term_char_set(obj->ch, ucs4);
265 * Returns: The previous character reset to @append_ucs4.
267 term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4) {
269 return char_build(TERM_CHAR_NULL, append_ucs4);
273 * term_char_merge() - Merge UCS-4 char at the end of an existing char
274 * @base: existing term-char
275 * @append_ucs4: UCS-4 character to append
277 * This appends @append_ucs4 to @base and returns the result. @base is
278 * invalidated by this function and must no longer be used. The returned value
279 * replaces the old one.
281 * Usually, this is used like this:
282 * obj->ch = term_char_merge(obj->ch, ucs4);
284 * Returns: The new merged character.
286 term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4) {
289 ch = char_build(base, append_ucs4);
290 if (!term_char_same(ch, base))
291 term_char_free(base);
297 * term_char_dup() - Duplicate character
298 * @ch: character to duplicate
300 * This duplicates a term-character. In case the character is not allocated,
301 * nothing is done. Otherwise, the underlying memory is copied and returned. You
302 * need to call term_char_free() on the returned character to release it again.
303 * On allocation errors, a replacement character is returned. Therefore, the
304 * caller can safely assume that this function always succeeds.
306 * Returns: The duplicated term-character.
308 term_char_t term_char_dup(term_char_t ch) {
309 term_character *c, *newc;
311 if (!term_char_is_allocated(ch))
315 newc = char_alloc(c->n);
317 return char_pack1(CHAR_UCS4_REPLACEMENT);
319 memcpy(newc->codepoints, c->codepoints, sizeof(*c->codepoints) * c->n);
320 return char_from_ptr(newc);
324 * term_char_dup_append() - Duplicate tsm-char with UCS-4 character appended
325 * @base: existing term-char
326 * @append_ucs4: UCS-4 character to append
328 * This is similar to term_char_merge(), but it returns a separately allocated
329 * character. That is, @base will stay valid after this returns and is not
330 * touched. In case the append-operation fails, @base is duplicated and
331 * returned. That is, the returned char is always independent of @base.
333 * Returns: Newly allocated character with @append_ucs4 appended to @base.
335 term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4) {
338 ch = char_build(base, append_ucs4);
339 if (term_char_same(ch, base))
340 ch = term_char_dup(base);
346 * term_char_resolve() - Retrieve the UCS-4 string for a term-char
347 * @ch: character to resolve
348 * @s: storage for size of string or NULL
349 * @b: storage for string or NULL
351 * This takes a term-character and returns the UCS-4 string associated with it.
352 * In case @ch is not allocated, the string is stored in @b (in case @b is NULL
353 * static storage is used). Otherwise, a pointer to the allocated storage is
356 * The returned string is only valid as long as @ch and @b are valid. The string
357 * is zero-terminated and can safely be printed via long-character printf().
358 * The length of the string excluding the zero-character is returned in @s.
360 * This never returns NULL. Even if the size is 0, this points to a buffer of at
361 * least a zero-terminator.
363 * Returns: The UCS-4 string-representation of @ch, and its size in @s.
365 const uint32_t *term_char_resolve(term_char_t ch, size_t *s, term_charbuf_t *b) {
366 static term_charbuf_t static_b;
374 cache = static_b.buf;
376 if (term_char_is_null(ch)) {
379 } else if (term_char_is_allocated(ch)) {
382 cache = c->codepoints;
384 len = char_unpack(ch, &cache[0], &cache[1], &cache[2]);
395 * term_char_lookup_width() - Lookup cell-width of a character
396 * @ch: character to return cell-width for
398 * This is an equivalent of wcwidth() for term_char_t. It can deal directly
399 * with UCS-4 and combining-characters and avoids the mess that is wchar_t and
402 * Returns: 0 for unprintable characters, >0 for everything else.
404 unsigned int term_char_lookup_width(term_char_t ch) {
412 str = term_char_resolve(ch, &len, &b);
414 for (i = 0; i < len; ++i) {
416 * Oh god, C99 locale handling strikes again: wcwidth() expects
417 * wchar_t, but there is no way for us to know the
418 * internal encoding of wchar_t. Moreover, it is nearly
419 * impossible to convert UCS-4 into wchar_t (except for iconv,
420 * which is way too much overhead).
421 * Therefore, we use our own copy of wcwidth(). Lets just hope
422 * that glibc will one day export it's internal UCS-4 and UTF-8
423 * helpers for direct use.
425 assert_cc(sizeof(wchar_t) >= 4);
426 r = mk_wcwidth((wchar_t)str[i]);
427 if (r > 0 && (unsigned int)r > max)
435 * term_cell_init() - Initialize a new cell
436 * @cell: cell to initialize
437 * @ch: character to set on the cell or TERM_CHAR_NULL
438 * @cwidth: character width of @ch
439 * @attr: attributes to set on the cell or NULL
440 * @age: age to set on the cell or TERM_AGE_NULL
442 * This initializes a new cell. The backing-memory of the cell must be allocated
443 * by the caller beforehand. The caller is responsible to destroy the cell via
444 * term_cell_destroy() before freeing the backing-memory.
446 * It is safe (and supported!) to use:
449 * term_cell_init(c, TERM_CHAR_NULL, NULL, TERM_AGE_NULL);
451 * Note that this call takes ownership of @ch. If you want to use it yourself
452 * after this call, you need to duplicate it before calling this.
454 static void term_cell_init(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
458 cell->cwidth = cwidth;
462 memcpy(&cell->attr, attr, sizeof(*attr));
468 * term_cell_destroy() - Destroy previously initialized cell
469 * @cell: cell to destroy or NULL
471 * This releases all resources associated with a cell. The backing memory is
472 * kept as-is. It's the responsibility of the caller to manage it.
474 * You must not call any other cell operations on this cell after this call
475 * returns. You must re-initialize the cell via term_cell_init() before you can
478 * If @cell is NULL, this is a no-op.
480 static void term_cell_destroy(term_cell *cell) {
484 term_char_free(cell->ch);
488 * term_cell_set() - Change contents of a cell
489 * @cell: cell to modify
490 * @ch: character to set on the cell or cell->ch
491 * @cwidth: character width of @ch or cell->cwidth
492 * @attr: attributes to set on the cell or NULL
493 * @age: age to set on the cell or cell->age
495 * This changes the contents of a cell. It can be used to change the character,
496 * attributes and age. To keep the current character, pass cell->ch as @ch. To
497 * reset the current attributes, pass NULL. To keep the current age, pass
500 * This call takes ownership of @ch. You need to duplicate it first, in case you
501 * want to use it for your own purposes after this call.
503 * The cell must have been initialized properly before calling this. See
506 static void term_cell_set(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
509 if (!term_char_same(ch, cell->ch)) {
510 term_char_free(cell->ch);
514 cell->cwidth = cwidth;
518 memcpy(&cell->attr, attr, sizeof(*attr));
524 * term_cell_append() - Append a combining-char to a cell
525 * @cell: cell to modify
526 * @ucs4: UCS-4 character to append to the cell
527 * @age: new age to set on the cell or cell->age
529 * This appends a combining-character to a cell. No validation of the UCS-4
530 * character is done, so this can be used to append any character. Additionally,
531 * this can update the age of the cell.
533 * The cell must have been initialized properly before calling this. See
536 static void term_cell_append(term_cell *cell, uint32_t ucs4, term_age_t age) {
539 cell->ch = term_char_merge(cell->ch, ucs4);
544 * term_cell_init_n() - Initialize an array of cells
545 * @cells: pointer to an array of cells to initialize
546 * @n: number of cells
547 * @attr: attributes to set on all cells or NULL
548 * @age: age to set on all cells
550 * This is the same as term_cell_init() but initializes an array of cells.
551 * Furthermore, this always sets the character to TERM_CHAR_NULL.
552 * If you want to set a specific characters on all cells, you need to hard-code
553 * this loop and duplicate the character for each cell.
555 static void term_cell_init_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) {
556 for ( ; n > 0; --n, ++cells)
557 term_cell_init(cells, TERM_CHAR_NULL, 0, attr, age);
561 * term_cell_destroy_n() - Destroy an array of cells
562 * @cells: pointer to an array of cells to destroy
563 * @n: number of cells
565 * This is the same as term_cell_destroy() but destroys an array of cells.
567 static void term_cell_destroy_n(term_cell *cells, unsigned int n) {
568 for ( ; n > 0; --n, ++cells)
569 term_cell_destroy(cells);
573 * term_cell_clear_n() - Clear contents of an array of cells
574 * @cells: pointer to an array of cells to modify
575 * @n: number of cells
576 * @attr: attributes to set on all cells or NULL
577 * @age: age to set on all cells
579 * This is the same as term_cell_set() but operates on an array of cells. Note
580 * that all characters are always set to TERM_CHAR_NULL, unlike term_cell_set()
581 * which takes the character as argument.
582 * If you want to set a specific characters on all cells, you need to hard-code
583 * this loop and duplicate the character for each cell.
585 static void term_cell_clear_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) {
586 for ( ; n > 0; --n, ++cells)
587 term_cell_set(cells, TERM_CHAR_NULL, 0, attr, age);
591 * term_line_new() - Allocate a new line
592 * @out: place to store pointer to new line
594 * This allocates and initialized a new line. The line is unlinked and
595 * independent of any page. It can be used for any purpose. The initial
596 * cell-count is set to 0.
598 * The line has to be freed via term_line_free() once it's no longer needed.
600 * Returns: 0 on success, negative error code on failure.
602 int term_line_new(term_line **out) {
603 _term_line_free_ term_line *line = NULL;
605 assert_return(out, -EINVAL);
607 line = new0(term_line, 1);
617 * term_line_free() - Free a line
618 * @line: line to free or NULL
620 * This frees a line that was previously allocated via term_line_free(). All its
621 * cells are released, too.
623 * If @line is NULL, this is a no-op.
625 term_line *term_line_free(term_line *line) {
629 term_cell_destroy_n(line->cells, line->n_cells);
637 * term_line_reserve() - Pre-allocate cells for a line
638 * @line: line to pre-allocate cells for
639 * @width: numbers of cells the line shall have pre-allocated
640 * @attr: attribute for all allocated cells or NULL
641 * @age: current age for all modifications
642 * @protect_width: width to protect from erasure
644 * This pre-allocates cells for this line. Please note that @width is the number
645 * of cells the line is guaranteed to have allocated after this call returns.
646 * It's not the number of cells that are added, neither is it the new width of
649 * This function never frees memory. That is, reducing the line-width will
650 * always succeed, same is true for increasing the width to a previously set
653 * @attr and @age are used to initialize new cells. Additionally, any
654 * existing cell outside of the protected area specified by @protect_width are
655 * cleared and reset with @attr and @age.
657 * Returns: 0 on success, negative error code on failure.
659 int term_line_reserve(term_line *line, unsigned int width, const term_attr *attr, term_age_t age, unsigned int protect_width) {
660 unsigned int min_width;
663 assert_return(line, -EINVAL);
665 /* reset existing cells if required */
666 min_width = MIN(line->n_cells, width);
667 if (min_width > protect_width)
668 term_cell_clear_n(line->cells + protect_width,
669 min_width - protect_width,
673 /* allocate new cells if required */
675 if (width > line->n_cells) {
676 t = realloc_multiply(line->cells, sizeof(*t), width);
681 memzero(t + line->n_cells,
682 sizeof(*t) * (width - line->n_cells));
684 term_cell_init_n(t + line->n_cells,
685 width - line->n_cells,
690 line->n_cells = width;
693 line->fill = MIN(line->fill, protect_width);
699 * term_line_set_width() - Change width of a line
700 * @line: line to modify
703 * This changes the actual width of a line. It is the caller's responsibility
704 * to use term_line_reserve() to make sure enough space is allocated. If @width
705 * is greater than the allocated size, it is cropped.
707 * This does not modify any cells. Use term_line_reserve() or term_line_erase()
708 * to clear any newly added cells.
710 * NOTE: The fill state is cropped at line->width. Therefore, if you increase
711 * the line-width afterwards, but there is a multi-cell character at the
712 * end of the line that got cropped, then the fill-state will _not_ be
714 * This means, the fill-state always includes the cells up to the start
715 * of the right-most character, but it might or might not cover it until
716 * its end. This should be totally fine, though. You should never access
717 * multi-cell tails directly, anyway.
719 void term_line_set_width(term_line *line, unsigned int width) {
722 if (width > line->n_cells)
723 width = line->n_cells;
726 line->fill = MIN(line->fill, width);
730 * line_insert() - Insert characters and move existing cells to the right
731 * @from: position to insert cells at
732 * @num: number of cells to insert
733 * @head_char: character that is set on the first cell
734 * @head_cwidth: character-length of @head_char
735 * @attr: attribute for all inserted cells or NULL
736 * @age: current age for all modifications
738 * The INSERT operation (or writes with INSERT_MODE) writes data at a specific
739 * position on a line and shifts the existing cells to the right. Cells that are
740 * moved beyond the right hand border are discarded.
742 * This helper contains the actual INSERT implementation which is independent of
743 * the data written. It works on cells, not on characters. The first cell is set
744 * to @head_char, all others are reset to TERM_CHAR_NULL. See each caller for a
745 * more detailed description.
747 static inline void line_insert(term_line *line, unsigned int from, unsigned int num, term_char_t head_char, unsigned int head_cwidth, const term_attr *attr, term_age_t age) {
748 unsigned int i, rem, move;
750 if (from >= line->width)
752 if (from + num < from || from + num > line->width)
753 num = line->width - from;
757 move = line->width - from - num;
758 rem = MIN(num, move);
762 * Make room for @num cells; shift cells to the right if
763 * required. @rem is the number of remaining cells that we will
764 * knock off on the right and overwrite during the right shift.
766 * For INSERT_MODE, @num/@rem are usually 1 or 2, @move is 50%
767 * of the line on average. Therefore, the actual move is quite
768 * heavy and we can safely invalidate cells manually instead of
770 * However, for INSERT operations, any parameters are
771 * possible. But we cannot place any assumption on its usage
772 * across applications, so we just handle it the same as
773 * INSERT_MODE and do per-cell invalidation.
776 /* destroy cells that are knocked off on the right */
777 term_cell_destroy_n(line->cells + line->width - rem, rem);
779 /* move remaining bulk of cells */
780 memmove(line->cells + from + num,
782 sizeof(*line->cells) * move);
784 /* invalidate cells */
785 for (i = 0; i < move; ++i)
786 line->cells[from + num + i].age = age;
788 /* initialize fresh head-cell */
789 term_cell_init(line->cells + from,
795 /* initialize fresh tail-cells */
796 term_cell_init_n(line->cells + from + 1,
801 /* adjust fill-state */
802 DISABLE_WARNING_SHADOW;
803 line->fill = MIN(line->width,
804 MAX(line->fill + num,
808 /* modify head-cell */
809 term_cell_set(line->cells + from,
815 /* reset tail-cells */
816 term_cell_clear_n(line->cells + from + 1,
821 /* adjust fill-state */
822 line->fill = line->width;
827 * term_line_write() - Write to a single, specific cell
828 * @line: line to write to
829 * @pos_x: x-position of cell in @line to write to
830 * @ch: character to write to the cell
831 * @cwidth: character width of @ch
832 * @attr: attributes to set on the cell or NULL
833 * @age: current age for all modifications
834 * @insert_mode: true if INSERT-MODE is enabled
836 * This writes to a specific cell in a line. The cell is addressed by its
837 * X-position @pos_x. If that cell does not exist, this is a no-op.
839 * @ch and @attr are set on this cell.
841 * If @insert_mode is true, this inserts the character instead of overwriting
842 * existing data (existing data is now moved to the right before writing).
844 * This function is the low-level handler of normal writes to a terminal.
846 void term_line_write(term_line *line, unsigned int pos_x, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode) {
851 if (pos_x >= line->width)
854 len = MAX(1U, cwidth);
855 if (pos_x + len < pos_x || pos_x + len > line->width)
856 len = line->width - pos_x;
861 /* Use line_insert() to insert the character-head and fill
862 * the remains with NULLs. */
863 line_insert(line, pos_x, len, ch, cwidth, attr, age);
865 /* modify head-cell */
866 term_cell_set(line->cells + pos_x, ch, cwidth, attr, age);
868 /* reset tail-cells */
869 term_cell_clear_n(line->cells + pos_x + 1,
874 /* adjust fill-state */
875 DISABLE_WARNING_SHADOW;
876 line->fill = MIN(line->width,
884 * term_line_insert() - Insert empty cells
885 * @line: line to insert empty cells into
886 * @from: x-position where to insert cells
887 * @num: number of cells to insert
888 * @attr: attributes to set on the cells or NULL
889 * @age: current age for all modifications
891 * This inserts @num empty cells at position @from in line @line. All existing
892 * cells to the right are shifted to make room for the new cells. Cells that get
893 * pushed beyond the right hand border are discarded.
895 void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) {
896 /* use line_insert() to insert @num empty cells */
897 return line_insert(line, from, num, TERM_CHAR_NULL, 0, attr, age);
901 * term_line_delete() - Delete cells from line
902 * @line: line to delete cells from
903 * @from: position to delete cells at
904 * @num: number of cells to delete
905 * @attr: attributes to set on any new cells
906 * @age: current age for all modifications
908 * Delete cells from a line. All cells to the right of the deleted cells are
909 * shifted to the left to fill the empty space. New cells appearing on the right
910 * hand border are cleared and initialized with @attr.
912 void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) {
913 unsigned int rem, move, i;
917 if (from >= line->width)
919 if (from + num < from || from + num > line->width)
920 num = line->width - from;
924 /* destroy and move as many upfront as possible */
925 move = line->width - from - num;
926 rem = MIN(num, move);
928 /* destroy to be removed cells */
929 term_cell_destroy_n(line->cells + from, rem);
931 /* move tail upfront */
932 memmove(line->cells + from,
933 line->cells + from + num,
934 sizeof(*line->cells) * move);
936 /* invalidate copied cells */
937 for (i = 0; i < move; ++i)
938 line->cells[from + i].age = age;
940 /* initialize tail that was moved away */
941 term_cell_init_n(line->cells + line->width - rem,
946 /* reset remaining cells in case the move was too small */
948 term_cell_clear_n(line->cells + from + move,
954 term_cell_clear_n(line->cells + from,
960 /* adjust fill-state */
961 if (from + num < line->fill)
963 else if (from < line->fill)
968 * term_line_append_combchar() - Append combining char to existing cell
969 * @line: line to modify
970 * @pos_x: position of cell to append combining char to
971 * @ucs4: combining character to append
972 * @age: current age for all modifications
974 * Unicode allows trailing combining characters, which belong to the
975 * char in front of them. The caller is responsible of detecting
976 * combining characters and calling term_line_append_combchar() instead of
977 * term_line_write(). This simply appends the char to the correct cell then.
978 * If the cell is not in the visible area, this call is skipped.
980 * Note that control-sequences are not 100% compatible with combining
981 * characters as they require delayed parsing. However, we must handle
982 * control-sequences immediately. Therefore, there might be trailing
983 * combining chars that should be discarded by the parser.
984 * However, to prevent programming errors, we're also being pedantic
985 * here and discard weirdly placed combining chars. This prevents
986 * situations were invalid content is parsed into the terminal and you
987 * might end up with cells containing only combining chars.
989 * Long story short: To get combining-characters working with old-fashioned
990 * terminal-emulation, we parse them exclusively for direct cell-writes. Other
991 * combining-characters are usually simply discarded and ignored.
993 void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age) {
996 if (pos_x >= line->width)
999 /* Unused cell? Skip appending any combining chars then. */
1000 if (term_char_is_null(line->cells[pos_x].ch))
1003 term_cell_append(line->cells + pos_x, ucs4, age);
1007 * term_line_erase() - Erase parts of a line
1008 * @line: line to modify
1009 * @from: position to start the erase
1010 * @num: number of cells to erase
1011 * @attr: attributes to initialize erased cells with
1012 * @age: current age for all modifications
1013 * @keep_protected: true if protected cells should be kept
1015 * This is the standard erase operation. It clears all cells in the targetted
1016 * area and re-initializes them. Cells to the right are not shifted left, you
1017 * must use DELETE to achieve that. Cells outside the visible area are skipped.
1019 * If @keep_protected is true, protected cells will not be erased.
1021 void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected) {
1023 unsigned int i, last_protected;
1027 if (from >= line->width)
1029 if (from + num < from || from + num > line->width)
1030 num = line->width - from;
1035 for (i = 0; i < num; ++i) {
1036 cell = line->cells + from + i;
1037 if (keep_protected && cell->attr.protect) {
1038 /* only count protected-cells inside the fill-region */
1039 if (from + i < line->fill)
1040 last_protected = from + i;
1045 term_cell_set(cell, TERM_CHAR_NULL, 0, attr, age);
1048 /* Adjust fill-state. This is a bit tricks, we can only adjust it in
1049 * case the erase-region starts inside the fill-region and ends at the
1050 * tail or beyond the fill-region. Otherwise, the current fill-state
1052 * Furthermore, we must account for protected cells. The loop above
1053 * ensures that protected-cells are only accounted for if they're
1054 * inside the fill-region. */
1055 if (from < line->fill && from + num >= line->fill)
1056 line->fill = MAX(from, last_protected);
1060 * term_line_reset() - Reset a line
1061 * @line: line to reset
1062 * @attr: attributes to initialize all cells with
1063 * @age: current age for all modifications
1065 * This resets all visible cells of a line and sets their attributes and ages
1066 * to @attr and @age. This is equivalent to erasing a whole line via
1067 * term_line_erase().
1069 void term_line_reset(term_line *line, const term_attr *attr, term_age_t age) {
1072 return term_line_erase(line, 0, line->width, attr, age, 0);
1076 * term_line_link() - Link line in front of a list
1077 * @line: line to link
1078 * @first: member pointing to first entry
1079 * @last: member pointing to last entry
1081 * This links a line into a list of lines. The line is inserted at the front and
1082 * must not be linked, yet. See the TERM_LINE_LINK() macro for an easier usage of
1085 void term_line_link(term_line *line, term_line **first, term_line **last) {
1089 assert(!line->lines_prev);
1090 assert(!line->lines_next);
1092 line->lines_prev = NULL;
1093 line->lines_next = *first;
1095 (*first)->lines_prev = line;
1102 * term_line_link_tail() - Link line at tail of a list
1103 * @line: line to link
1104 * @first: member pointing to first entry
1105 * @last: member pointing to last entry
1107 * Same as term_line_link() but links the line at the tail.
1109 void term_line_link_tail(term_line *line, term_line **first, term_line **last) {
1113 assert(!line->lines_prev);
1114 assert(!line->lines_next);
1116 line->lines_next = NULL;
1117 line->lines_prev = *last;
1119 (*last)->lines_next = line;
1126 * term_line_unlink() - Unlink line from a list
1127 * @line: line to unlink
1128 * @first: member pointing to first entry
1129 * @last: member pointing to last entry
1131 * This unlinks a previously linked line. See TERM_LINE_UNLINK() for an easier to
1134 void term_line_unlink(term_line *line, term_line **first, term_line **last) {
1139 if (line->lines_prev)
1140 line->lines_prev->lines_next = line->lines_next;
1142 *first = line->lines_next;
1143 if (line->lines_next)
1144 line->lines_next->lines_prev = line->lines_prev;
1146 *last = line->lines_prev;
1148 line->lines_prev = NULL;
1149 line->lines_next = NULL;
1153 * term_page_new() - Allocate new page
1154 * @out: storage for pointer to new page
1156 * Allocate a new page. The initial dimensions are 0/0.
1158 * Returns: 0 on success, negative error code on failure.
1160 int term_page_new(term_page **out) {
1161 _term_page_free_ term_page *page = NULL;
1163 assert_return(out, -EINVAL);
1165 page = new0(term_page, 1);
1175 * term_page_free() - Free page
1176 * @page: page to free or NULL
1178 * Free a previously allocated page and all associated data. If @page is NULL,
1183 term_page *term_page_free(term_page *page) {
1189 for (i = 0; i < page->n_lines; ++i)
1190 term_line_free(page->lines[i]);
1192 free(page->line_cache);
1200 * term_page_get_cell() - Return pointer to requested cell
1201 * @page: page to operate on
1202 * @x: x-position of cell
1203 * @y: y-position of cell
1205 * This returns a pointer to the cell at position @x/@y. You're free to modify
1206 * this cell as much as you like. However, once you call any other function on
1207 * the page, you must drop the pointer to the cell.
1209 * Returns: Pointer to the cell or NULL if out of the visible area.
1211 term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y) {
1212 assert_return(page, NULL);
1214 if (x >= page->width)
1216 if (y >= page->height)
1219 return &page->lines[y]->cells[x];
1223 * page_scroll_up() - Scroll up
1224 * @page: page to operate on
1225 * @new_width: width to use for any new line moved into the visible area
1226 * @num: number of lines to scroll up
1227 * @attr: attributes to set on new lines
1228 * @age: age to use for all modifications
1229 * @history: history to use for old lines or NULL
1231 * This scrolls the scroll-region by @num lines. New lines are cleared and reset
1232 * with the given attributes. Old lines are moved into the history if non-NULL.
1233 * If a new line is allocated, moved from the history buffer or moved from
1234 * outside the visible region into the visible region, this call makes sure it
1235 * has at least @width cells allocated. If a possible memory-allocation fails,
1236 * the previous line is reused. This has the side effect, that it will not be
1237 * linked into the history buffer.
1239 * If the scroll-region is empty, this is a no-op.
1241 static void page_scroll_up(term_page *page, unsigned int new_width, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1242 term_line *line, **cache;
1248 if (num > page->scroll_num)
1249 num = page->scroll_num;
1253 /* Better safe than sorry: avoid under-allocating lines, even when
1255 new_width = MAX(new_width, page->width);
1257 cache = page->line_cache;
1259 /* Try moving lines into history and allocate new lines for each moved
1260 * line. In case allocation fails, or if we have no history, reuse the
1262 * We keep the lines in the line-cache so we can safely move the
1263 * remaining lines around. */
1264 for (i = 0; i < num; ++i) {
1265 line = page->lines[page->scroll_idx + i];
1269 r = term_line_new(&cache[i]);
1271 r = term_line_reserve(cache[i],
1277 term_line_free(cache[i]);
1279 term_line_set_width(cache[i], page->width);
1284 term_history_push(history, line);
1287 term_line_reset(line, attr, age);
1291 if (num < page->scroll_num) {
1292 memmove(page->lines + page->scroll_idx,
1293 page->lines + page->scroll_idx + num,
1294 sizeof(*page->lines) * (page->scroll_num - num));
1296 /* update age of moved lines */
1297 for (i = 0; i < page->scroll_num - num; ++i)
1298 page->lines[page->scroll_idx + i]->age = age;
1301 /* copy remaining lines from cache; age is already updated */
1302 memcpy(page->lines + page->scroll_idx + page->scroll_num - num,
1304 sizeof(*cache) * num);
1307 page->scroll_fill -= MIN(page->scroll_fill, num);
1311 * page_scroll_down() - Scroll down
1312 * @page: page to operate on
1313 * @new_width: width to use for any new line moved into the visible area
1314 * @num: number of lines to scroll down
1315 * @attr: attributes to set on new lines
1316 * @age: age to use for all modifications
1317 * @history: history to use for new lines or NULL
1319 * This scrolls the scroll-region by @num lines. New lines are retrieved from
1320 * the history or cleared if the history is empty or NULL.
1322 * Usually, scroll-down implies that new lines are cleared. Therefore, you're
1323 * highly encouraged to set @history to NULL. However, if you resize a terminal,
1324 * you might want to include history-lines in the new area. In that case, you
1325 * should set @history to non-NULL.
1327 * If a new line is allocated, moved from the history buffer or moved from
1328 * outside the visible region into the visible region, this call makes sure it
1329 * has at least @width cells allocated. If a possible memory-allocation fails,
1330 * the previous line is reused. This will have the side-effect that lines from
1331 * the history will not get visible on-screen but kept in history.
1333 * If the scroll-region is empty, this is a no-op.
1335 static void page_scroll_down(term_page *page, unsigned int new_width, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1336 term_line *line, **cache, *t;
1337 unsigned int i, last_idx;
1341 if (num > page->scroll_num)
1342 num = page->scroll_num;
1346 /* Better safe than sorry: avoid under-allocating lines, even when
1348 new_width = MAX(new_width, page->width);
1350 cache = page->line_cache;
1351 last_idx = page->scroll_idx + page->scroll_num - 1;
1353 /* Try pulling out lines from history; if history is empty or if no
1354 * history is given, we reuse the to-be-removed lines. Otherwise, those
1355 * lines are released. */
1356 for (i = 0; i < num; ++i) {
1357 line = page->lines[last_idx - i];
1361 t = term_history_pop(history, new_width, attr, age);
1364 cache[num - 1 - i] = t;
1365 term_line_free(line);
1367 cache[num - 1 - i] = line;
1368 term_line_reset(line, attr, age);
1372 if (num < page->scroll_num) {
1373 memmove(page->lines + page->scroll_idx + num,
1374 page->lines + page->scroll_idx,
1375 sizeof(*page->lines) * (page->scroll_num - num));
1377 /* update age of moved lines */
1378 for (i = 0; i < page->scroll_num - num; ++i)
1379 page->lines[page->scroll_idx + num + i]->age = age;
1382 /* copy remaining lines from cache; age is already updated */
1383 memcpy(page->lines + page->scroll_idx,
1385 sizeof(*cache) * num);
1387 /* update fill; but only if there's already content in it */
1388 if (page->scroll_fill > 0)
1389 page->scroll_fill = MIN(page->scroll_num,
1390 page->scroll_fill + num);
1394 * page_reserve() - Reserve page area
1395 * @page: page to modify
1396 * @cols: required columns (width)
1397 * @rows: required rows (height)
1398 * @attr: attributes for newly allocated cells
1399 * @age: age to set on any modified cells
1401 * This allocates the required amount of lines and cells to guarantee that the
1402 * page has at least the demanded dimensions of @cols x @rows. Note that this
1403 * never shrinks the page-memory. We keep cells allocated for performance
1406 * Additionally to allocating lines, this also clears any newly added cells so
1407 * you can safely change the size afterwards without clearing new cells.
1409 * Note that you must be careful what operations you call on the page between
1410 * page_reserve() and updating page->width/height. Any newly allocated line (or
1411 * shifted line) might not meet your new width/height expectations.
1413 * Returns: 0 on success, negative error code on failure.
1415 int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age) {
1416 _term_line_free_ term_line *line = NULL;
1417 unsigned int i, min_lines;
1421 assert_return(page, -EINVAL);
1424 * First make sure the first MIN(page->n_lines, rows) lines have at
1425 * least the required width of @cols. This does not modify any visible
1426 * cells in the existing @page->width x @page->height area, therefore,
1427 * we can safely bail out afterwards in case anything else fails.
1428 * Note that lines in between page->height and page->n_lines might be
1429 * shorter than page->width. Hence, we need to resize them all, but we
1430 * can skip some of them for better performance.
1432 min_lines = MIN(page->n_lines, rows);
1433 for (i = 0; i < min_lines; ++i) {
1434 /* lines below page->height have at least page->width cells */
1435 if (cols < page->width && i < page->height)
1438 r = term_line_reserve(page->lines[i],
1442 (i < page->height) ? page->width : 0);
1448 * We now know the first @min_lines lines have at least width @cols and
1449 * are prepared for resizing. We now only have to allocate any
1450 * additional lines below @min_lines in case @rows is greater than
1453 if (rows > page->n_lines) {
1454 t = realloc_multiply(page->lines, sizeof(*t), rows);
1459 t = realloc_multiply(page->line_cache, sizeof(*t), rows);
1462 page->line_cache = t;
1464 while (page->n_lines < rows) {
1465 r = term_line_new(&line);
1469 r = term_line_reserve(line, cols, attr, age, 0);
1473 page->lines[page->n_lines++] = line;
1482 * term_page_resize() - Resize page
1483 * @page: page to modify
1484 * @cols: number of columns (width)
1485 * @rows: number of rows (height)
1486 * @attr: attributes for newly allocated cells
1487 * @age: age to set on any modified cells
1488 * @history: history buffer to use for new/old lines or NULL
1490 * This changes the visible dimensions of a page. You must have called
1491 * term_page_reserve() beforehand, otherwise, this will fail.
1493 * Returns: 0 on success, negative error code on failure.
1495 void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history) {
1496 unsigned int i, num, empty, max, old_height;
1500 assert(page->n_lines >= rows);
1502 old_height = page->height;
1504 if (rows < old_height) {
1506 * If we decrease the terminal-height, we emulate a scroll-up.
1507 * This way, existing data from the scroll-area is moved into
1508 * the history, making space at the bottom to reduce the screen
1509 * height. In case the scroll-fill indicates empty lines, we
1510 * reduce the amount of scrolled lines.
1511 * Once scrolled, we have to move the lower margin from below
1512 * the scroll area up so it is preserved.
1515 /* move lines to history if scroll region is filled */
1516 num = old_height - rows;
1517 empty = page->scroll_num - page->scroll_fill;
1519 page_scroll_up(page,
1526 /* move lower margin up; drop its lines if not enough space */
1527 num = LESS_BY(old_height, page->scroll_idx + page->scroll_num);
1528 max = LESS_BY(rows, page->scroll_idx);
1529 num = MIN(num, max);
1531 unsigned int top, bottom;
1534 bottom = page->scroll_idx + page->scroll_num;
1536 /* might overlap; must run topdown, not bottomup */
1537 for (i = 0; i < num; ++i) {
1538 line = page->lines[top + i];
1539 page->lines[top + i] = page->lines[bottom + i];
1540 page->lines[bottom + i] = line;
1544 /* update vertical extents */
1545 page->height = rows;
1546 page->scroll_idx = MIN(page->scroll_idx, rows);
1547 page->scroll_num -= MIN(page->scroll_num, old_height - rows);
1548 /* fill is already up-to-date or 0 due to scroll-up */
1549 } else if (rows > old_height) {
1551 * If we increase the terminal-height, we emulate a scroll-down
1552 * and fetch new lines from the history.
1553 * New lines are always accounted to the scroll-region. Thus we
1554 * have to preserve the lower margin first, by moving it down.
1557 /* move lower margin down */
1558 num = LESS_BY(old_height, page->scroll_idx + page->scroll_num);
1560 unsigned int top, bottom;
1562 top = page->scroll_idx + page->scroll_num;
1563 bottom = top + (rows - old_height);
1565 /* might overlap; must run bottomup, not topdown */
1566 for (i = num; i-- > 0; ) {
1567 line = page->lines[top + i];
1568 page->lines[top + i] = page->lines[bottom + i];
1569 page->lines[bottom + i] = line;
1573 /* update vertical extents */
1574 page->height = rows;
1575 page->scroll_num = MIN(LESS_BY(rows, page->scroll_idx),
1576 page->scroll_num + (rows - old_height));
1578 /* check how many lines can be received from history */
1580 num = term_history_peek(history,
1588 /* retrieve new lines from history if available */
1590 page_scroll_down(page,
1598 /* set horizontal extents */
1600 for (i = 0; i < page->height; ++i)
1601 term_line_set_width(page->lines[i], cols);
1605 * term_page_write() - Write to a single cell
1606 * @page: page to operate on
1607 * @pos_x: x-position of cell to write to
1608 * @pos_y: y-position of cell to write to
1609 * @ch: character to write
1610 * @cwidth: character-width of @ch
1611 * @attr: attributes to set on the cell or NULL
1612 * @age: age to use for all modifications
1613 * @insert_mode: true if INSERT-MODE is enabled
1615 * This writes a character to a specific cell. If the cell is beyond bounds,
1616 * this is a no-op. @attr and @age are used to update the cell. @flags can be
1617 * used to alter the behavior of this function.
1619 * This is a wrapper around term_line_write().
1621 * This call does not wrap around lines. That is, this only operates on a single
1624 void term_page_write(term_page *page, unsigned int pos_x, unsigned int pos_y, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode) {
1627 if (pos_y >= page->height)
1630 term_line_write(page->lines[pos_y], pos_x, ch, cwidth, attr, age, insert_mode);
1634 * term_page_insert_cells() - Insert cells into a line
1635 * @page: page to operate on
1636 * @from_x: x-position where to insert new cells
1637 * @from_y: y-position where to insert new cells
1638 * @num: number of cells to insert
1639 * @attr: attributes to set on new cells or NULL
1640 * @age: age to use for all modifications
1642 * This inserts new cells into a given line. This is a wrapper around
1643 * term_line_insert().
1645 * This call does not wrap around lines. That is, this only operates on a single
1648 void term_page_insert_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age) {
1651 if (from_y >= page->height)
1654 term_line_insert(page->lines[from_y], from_x, num, attr, age);
1658 * term_page_delete_cells() - Delete cells from a line
1659 * @page: page to operate on
1660 * @from_x: x-position where to delete cells
1661 * @from_y: y-position where to delete cells
1662 * @num: number of cells to delete
1663 * @attr: attributes to set on new cells or NULL
1664 * @age: age to use for all modifications
1666 * This deletes cells from a given line. This is a wrapper around
1667 * term_line_delete().
1669 * This call does not wrap around lines. That is, this only operates on a single
1672 void term_page_delete_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age) {
1675 if (from_y >= page->height)
1678 term_line_delete(page->lines[from_y], from_x, num, attr, age);
1682 * term_page_append_combchar() - Append combining-character to a cell
1683 * @page: page to operate on
1684 * @pos_x: x-position of target cell
1685 * @pos_y: y-position of target cell
1686 * @ucs4: combining character to append
1687 * @age: age to use for all modifications
1689 * This appends a combining-character to a specific cell. This is a wrapper
1690 * around term_line_append_combchar().
1692 void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age) {
1695 if (pos_y >= page->height)
1698 term_line_append_combchar(page->lines[pos_y], pos_x, ucs4, age);
1702 * term_page_erase() - Erase parts of a page
1703 * @page: page to operate on
1704 * @from_x: x-position where to start erasure (inclusive)
1705 * @from_y: y-position where to start erasure (inclusive)
1706 * @to_x: x-position where to stop erasure (inclusive)
1707 * @to_y: y-position where to stop erasure (inclusive)
1708 * @attr: attributes to set on cells
1709 * @age: age to use for all modifications
1710 * @keep_protected: true if protected cells should be kept
1712 * This erases all cells starting at @from_x/@from_y up to @to_x/@to_y. Note
1713 * that this wraps around line-boundaries so lines between @from_y and @to_y
1714 * are cleared entirely.
1716 * Lines outside the visible area are left untouched.
1718 void term_page_erase(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int to_x, unsigned int to_y, const term_attr *attr, term_age_t age, bool keep_protected) {
1719 unsigned int i, from, to;
1723 for (i = from_y; i <= to_y && i < page->height; ++i) {
1732 term_line_erase(page->lines[i],
1742 * term_page_reset() - Reset page
1743 * @page: page to modify
1744 * @attr: attributes to set on cells
1745 * @age: age to use for all modifications
1747 * This erases the whole visible page. See term_page_erase().
1749 void term_page_reset(term_page *page, const term_attr *attr, term_age_t age) {
1752 return term_page_erase(page,
1754 page->width - 1, page->height - 1,
1761 * term_page_set_scroll_region() - Set scroll region
1762 * @page: page to operate on
1763 * @idx: start-index of scroll region
1764 * @num: number of lines in scroll region
1766 * This sets the scroll region of a page. Whenever an operation needs to scroll
1767 * lines, it scrolls them inside of that region. Lines outside the region are
1768 * left untouched. In case a scroll-operation is targeted outside of this
1769 * region, it will implicitly get a scroll-region of only one line (i.e., no
1770 * scroll region at all).
1772 * Note that the scroll-region is clipped to the current page-extents. Growing
1773 * or shrinking the page always accounts new/old lines to the scroll region and
1774 * moves top/bottom margins accordingly so they're preserved.
1776 void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num) {
1779 if (page->height < 1) {
1780 page->scroll_idx = 0;
1781 page->scroll_num = 0;
1783 page->scroll_idx = MIN(idx, page->height - 1);
1784 page->scroll_num = MIN(num, page->height - page->scroll_idx);
1789 * term_page_scroll_up() - Scroll up
1790 * @page: page to operate on
1791 * @num: number of lines to scroll up
1792 * @attr: attributes to set on new lines
1793 * @age: age to use for all modifications
1794 * @history: history to use for old lines or NULL
1796 * This scrolls the scroll-region by @num lines. New lines are cleared and reset
1797 * with the given attributes. Old lines are moved into the history if non-NULL.
1799 * If the scroll-region is empty, this is a no-op.
1801 void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1802 page_scroll_up(page, page->width, num, attr, age, history);
1806 * term_page_scroll_down() - Scroll down
1807 * @page: page to operate on
1808 * @num: number of lines to scroll down
1809 * @attr: attributes to set on new lines
1810 * @age: age to use for all modifications
1811 * @history: history to use for new lines or NULL
1813 * This scrolls the scroll-region by @num lines. New lines are retrieved from
1814 * the history or cleared if the history is empty or NULL.
1816 * Usually, scroll-down implies that new lines are cleared. Therefore, you're
1817 * highly encouraged to set @history to NULL. However, if you resize a terminal,
1818 * you might want to include history-lines in the new area. In that case, you
1819 * should set @history to non-NULL.
1821 * If the scroll-region is empty, this is a no-op.
1823 void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1824 page_scroll_down(page, page->width, num, attr, age, history);
1828 * term_page_insert_lines() - Insert new lines
1829 * @page: page to operate on
1830 * @pos_y: y-position where to insert new lines
1831 * @num: number of lines to insert
1832 * @attr: attributes to set on new lines
1833 * @age: age to use for all modifications
1835 * This inserts @num new lines at position @pos_y. If @pos_y is beyond
1836 * boundaries or @num is 0, this is a no-op.
1837 * All lines below @pos_y are moved down to make space for the new lines. Lines
1838 * on the bottom are dropped. Note that this only moves lines above or inside
1839 * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of
1840 * one line is implied (which means the line is simply cleared).
1842 void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) {
1843 unsigned int scroll_idx, scroll_num;
1847 if (pos_y >= page->height)
1849 if (num >= page->height)
1852 /* remember scroll-region */
1853 scroll_idx = page->scroll_idx;
1854 scroll_num = page->scroll_num;
1856 /* set scroll-region temporarily so we can reuse scroll_down() */
1858 page->scroll_idx = pos_y;
1859 if (pos_y >= scroll_idx + scroll_num)
1860 page->scroll_num = 1;
1861 else if (pos_y >= scroll_idx)
1862 page->scroll_num -= pos_y - scroll_idx;
1864 page->scroll_num += scroll_idx - pos_y;
1866 term_page_scroll_down(page, num, attr, age, NULL);
1869 /* reset scroll-region */
1870 page->scroll_idx = scroll_idx;
1871 page->scroll_num = scroll_num;
1875 * term_page_delete_lines() - Delete lines
1876 * @page: page to operate on
1877 * @pos_y: y-position where to delete lines
1878 * @num: number of lines to delete
1879 * @attr: attributes to set on new lines
1880 * @age: age to use for all modifications
1882 * This deletes @num lines at position @pos_y. If @pos_y is beyond boundaries or
1883 * @num is 0, this is a no-op.
1884 * All lines below @pos_y are moved up into the newly made space. New lines
1885 * on the bottom are clear. Note that this only moves lines above or inside
1886 * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of
1887 * one line is implied (which means the line is simply cleared).
1889 void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) {
1890 unsigned int scroll_idx, scroll_num;
1894 if (pos_y >= page->height)
1896 if (num >= page->height)
1899 /* remember scroll-region */
1900 scroll_idx = page->scroll_idx;
1901 scroll_num = page->scroll_num;
1903 /* set scroll-region temporarily so we can reuse scroll_up() */
1905 page->scroll_idx = pos_y;
1906 if (pos_y >= scroll_idx + scroll_num)
1907 page->scroll_num = 1;
1908 else if (pos_y > scroll_idx)
1909 page->scroll_num -= pos_y - scroll_idx;
1911 page->scroll_num += scroll_idx - pos_y;
1913 term_page_scroll_up(page, num, attr, age, NULL);
1916 /* reset scroll-region */
1917 page->scroll_idx = scroll_idx;
1918 page->scroll_num = scroll_num;
1922 * term_history_new() - Create new history object
1923 * @out: storage for pointer to new history
1925 * Create a new history object. Histories are used to store scrollback-lines
1926 * from VTE pages. You're highly recommended to set a history-limit on
1927 * history->max_lines and trim it via term_history_trim(), otherwise history
1928 * allocations are unlimited.
1930 * Returns: 0 on success, negative error code on failure.
1932 int term_history_new(term_history **out) {
1933 _term_history_free_ term_history *history = NULL;
1935 assert_return(out, -EINVAL);
1937 history = new0(term_history, 1);
1941 history->max_lines = 4096;
1949 * term_history_free() - Free history
1950 * @history: history to free
1952 * Clear and free history. You must not access the object afterwards.
1956 term_history *term_history_free(term_history *history) {
1960 term_history_clear(history);
1966 * term_history_clear() - Clear history
1967 * @history: history to clear
1969 * Remove all linked lines from a history and reset it to its initial state.
1971 void term_history_clear(term_history *history) {
1972 return term_history_trim(history, 0);
1976 * term_history_trim() - Trim history
1977 * @history: history to trim
1978 * @max: maximum number of lines to be left in history
1980 * This removes lines from the history until it is smaller than @max. Lines are
1981 * removed from the top.
1983 void term_history_trim(term_history *history, unsigned int max) {
1989 while (history->n_lines > max && (line = history->lines_first)) {
1990 TERM_LINE_UNLINK(line, history);
1991 term_line_free(line);
1997 * term_history_push() - Push line into history
1998 * @history: history to work on
1999 * @line: line to push into history
2001 * This pushes a line into the given history. It is linked at the tail. In case
2002 * the history is limited, the top-most line might be freed.
2004 void term_history_push(term_history *history, term_line *line) {
2008 TERM_LINE_LINK_TAIL(line, history);
2009 if (history->max_lines > 0 && history->n_lines >= history->max_lines) {
2010 line = history->lines_first;
2011 TERM_LINE_UNLINK(line, history);
2012 term_line_free(line);
2019 * term_history_pop() - Retrieve last line from history
2020 * @history: history to work on
2021 * @new_width: width to reserve and set on the line
2022 * @attr: attributes to use for cell reservation
2023 * @age: age to use for cell reservation
2025 * This unlinks the last linked line of the history and returns it. This also
2026 * makes sure the line has the given width pre-allocated (see
2027 * term_line_reserve()). If the pre-allocation fails, this returns NULL, so it
2028 * is treated like there's no line in history left. This simplifies
2029 * history-handling on the caller's side in case of allocation errors. No need
2030 * to throw lines away just because the reservation failed. We can keep them in
2031 * history safely, and make them available as scrollback.
2033 * Returns: Line from history or NULL
2035 term_line *term_history_pop(term_history *history, unsigned int new_width, const term_attr *attr, term_age_t age) {
2039 assert_return(history, NULL);
2041 line = history->lines_last;
2045 r = term_line_reserve(line, new_width, attr, age, line->width);
2049 term_line_set_width(line, new_width);
2050 TERM_LINE_UNLINK(line, history);
2057 * term_history_peek() - Return number of available history-lines
2058 * @history: history to work on
2059 * @max: maximum number of lines to look at
2060 * @reserve_width: width to reserve on the line
2061 * @attr: attributes to use for cell reservation
2062 * @age: age to use for cell reservation
2064 * This returns the number of available lines in the history given as @history.
2065 * It returns at most @max. For each line that is looked at, the line is
2066 * verified to have at least @reserve_width cells. Valid cells are preserved,
2067 * new cells are initialized with @attr and @age. In case an allocation fails,
2068 * we bail out and return the number of lines that are valid so far.
2070 * Usually, this function should be used before running a loop on
2071 * term_history_pop(). This function guarantees that term_history_pop() (with
2072 * the same arguments) will succeed at least the returned number of times.
2074 * Returns: Number of valid lines that can be received via term_history_pop().
2076 unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age) {
2084 line = history->lines_last;
2086 while (num < max && line) {
2087 r = term_line_reserve(line, reserve_width, attr, age, line->width);
2092 line = line->lines_prev;