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 * surrounding 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.
79 #include "term-internal.h"
82 /* maximum UCS-4 character */
83 #define CHAR_UCS4_MAX (0x10ffff)
84 /* mask for valid UCS-4 characters (21bit) */
85 #define CHAR_UCS4_MASK (0x1fffff)
86 /* UCS-4 replacement character */
87 #define CHAR_UCS4_REPLACEMENT (0xfffd)
89 /* real storage behind "term_char_t" in case it's not packed */
90 typedef struct term_character {
92 uint32_t codepoints[];
96 * char_pack() takes 3 UCS-4 values and packs them into a term_char_t object.
97 * Note that UCS-4 chars only take 21 bits, so we still have the LSB as marker.
98 * We set it to 1 so others can distinguish it from pointers.
100 static inline term_char_t char_pack(uint32_t v1, uint32_t v2, uint32_t v3) {
101 uint64_t packed, u1, u2, u3;
108 packed |= (u1 & (uint64_t)CHAR_UCS4_MASK) << 43;
109 packed |= (u2 & (uint64_t)CHAR_UCS4_MASK) << 22;
110 packed |= (u3 & (uint64_t)CHAR_UCS4_MASK) << 1;
112 return TERM_CHAR_INIT(packed);
115 #define char_pack1(_v1) char_pack2((_v1), CHAR_UCS4_MAX + 1)
116 #define char_pack2(_v1, _v2) char_pack3((_v1), (_v2), CHAR_UCS4_MAX + 1)
117 #define char_pack3(_v1, _v2, _v3) char_pack((_v1), (_v2), (_v3))
120 * char_unpack() is the inverse of char_pack(). It extracts the 3 stored UCS-4
121 * characters and returns them. Note that this does not validate the passed
122 * term_char_t. That's the responsibility of the caller.
123 * This returns the number of characters actually packed. This obviously is a
124 * number between 0 and 3 (inclusive).
126 static inline uint8_t char_unpack(term_char_t packed, uint32_t *out_v1, uint32_t *out_v2, uint32_t *out_v3) {
129 v1 = (packed._value >> 43) & (uint64_t)CHAR_UCS4_MASK;
130 v2 = (packed._value >> 22) & (uint64_t)CHAR_UCS4_MASK;
131 v3 = (packed._value >> 1) & (uint64_t)CHAR_UCS4_MASK;
140 return (v1 > CHAR_UCS4_MAX) ? 0 :
141 ((v2 > CHAR_UCS4_MAX) ? 1 :
142 ((v3 > CHAR_UCS4_MAX) ? 2 :
146 /* cast a term_char_t to a term_character* */
147 static inline term_character *char_to_ptr(term_char_t ch) {
148 return (term_character*)(unsigned long)ch._value;
151 /* cast a term_character* to a term_char_t */
152 static inline term_char_t char_from_ptr(term_character *c) {
153 return TERM_CHAR_INIT((unsigned long)c);
157 * char_alloc() allocates a properly aligned term_character object and returns
158 * a pointer to it. NULL is returned on allocation errors. The object will have
159 * enough room for @n following UCS-4 chars.
160 * Note that we allocate (n+1) characters and set the last one to 0 in case
161 * anyone prints this string for debugging.
163 static term_character *char_alloc(uint8_t n) {
167 r = posix_memalign((void**)&c,
168 MAX(sizeof(void*), (size_t)2),
169 sizeof(*c) + sizeof(*c->codepoints) * (n + 1));
174 c->codepoints[n] = 0;
180 * char_free() frees the memory allocated via char_alloc(). It is safe to call
181 * this on any term_char_t, only allocated characters are freed.
183 static inline void char_free(term_char_t ch) {
184 if (term_char_is_allocated(ch))
185 free(char_to_ptr(ch));
189 * This appends @append_ucs4 to the existing character @base and returns
190 * it as a new character. In case that's not possible, @base is returned. The
191 * caller can use term_char_same() to test whether the returned character was
192 * freshly allocated or not.
194 static term_char_t char_build(term_char_t base, uint32_t append_ucs4) {
195 /* soft-limit for combining-chars; hard-limit is currently 255 */
196 const size_t climit = 64;
201 /* ignore invalid UCS-4 */
202 if (append_ucs4 > CHAR_UCS4_MAX)
205 if (term_char_is_null(base)) {
206 return char_pack1(append_ucs4);
207 } else if (!term_char_is_allocated(base)) {
208 /* unpack and try extending the packed character */
209 n = char_unpack(base, &buf[0], &buf[1], &buf[2]);
213 return char_pack1(append_ucs4);
218 return char_pack2(buf[0], append_ucs4);
223 return char_pack3(buf[0], buf[1], append_ucs4);
229 /* already fully packed, we need to allocate a new one */
232 /* already an allocated type, we need to allocate a new one */
233 c = char_to_ptr(base);
238 /* bail out if soft-limit is reached */
242 /* allocate new char */
243 c = char_alloc(n + 1);
247 memcpy(c->codepoints, t, sizeof(*t) * n);
248 c->codepoints[n] = append_ucs4;
250 return char_from_ptr(c);
254 * term_char_set() - Reset character to a single UCS-4 character
255 * @previous: term-char to reset
256 * @append_ucs4: UCS-4 char to set
258 * This frees all resources in @previous and re-initializes it to @append_ucs4.
259 * The new char is returned.
261 * Usually, this is used like this:
262 * obj->ch = term_char_set(obj->ch, ucs4);
264 * Returns: The previous character reset to @append_ucs4.
266 term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4) {
268 return char_build(TERM_CHAR_NULL, append_ucs4);
272 * term_char_merge() - Merge UCS-4 char at the end of an existing char
273 * @base: existing term-char
274 * @append_ucs4: UCS-4 character to append
276 * This appends @append_ucs4 to @base and returns the result. @base is
277 * invalidated by this function and must no longer be used. The returned value
278 * replaces the old one.
280 * Usually, this is used like this:
281 * obj->ch = term_char_merge(obj->ch, ucs4);
283 * Returns: The new merged character.
285 term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4) {
288 ch = char_build(base, append_ucs4);
289 if (!term_char_same(ch, base))
290 term_char_free(base);
296 * term_char_dup() - Duplicate character
297 * @ch: character to duplicate
299 * This duplicates a term-character. In case the character is not allocated,
300 * nothing is done. Otherwise, the underlying memory is copied and returned. You
301 * need to call term_char_free() on the returned character to release it again.
302 * On allocation errors, a replacement character is returned. Therefore, the
303 * caller can safely assume that this function always succeeds.
305 * Returns: The duplicated term-character.
307 term_char_t term_char_dup(term_char_t ch) {
308 term_character *c, *newc;
310 if (!term_char_is_allocated(ch))
314 newc = char_alloc(c->n);
316 return char_pack1(CHAR_UCS4_REPLACEMENT);
318 memcpy(newc->codepoints, c->codepoints, sizeof(*c->codepoints) * c->n);
319 return char_from_ptr(newc);
323 * term_char_dup_append() - Duplicate tsm-char with UCS-4 character appended
324 * @base: existing term-char
325 * @append_ucs4: UCS-4 character to append
327 * This is similar to term_char_merge(), but it returns a separately allocated
328 * character. That is, @base will stay valid after this returns and is not
329 * touched. In case the append-operation fails, @base is duplicated and
330 * returned. That is, the returned char is always independent of @base.
332 * Returns: Newly allocated character with @append_ucs4 appended to @base.
334 term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4) {
337 ch = char_build(base, append_ucs4);
338 if (term_char_same(ch, base))
339 ch = term_char_dup(base);
345 * term_char_resolve() - Retrieve the UCS-4 string for a term-char
346 * @ch: character to resolve
347 * @s: storage for size of string or NULL
348 * @b: storage for string or NULL
350 * This takes a term-character and returns the UCS-4 string associated with it.
351 * In case @ch is not allocated, the string is stored in @b (in case @b is NULL
352 * static storage is used). Otherwise, a pointer to the allocated storage is
355 * The returned string is only valid as long as @ch and @b are valid. The string
356 * is zero-terminated and can safely be printed via long-character printf().
357 * The length of the string excluding the zero-character is returned in @s.
359 * This never returns NULL. Even if the size is 0, this points to a buffer of at
360 * least a zero-terminator.
362 * Returns: The UCS-4 string-representation of @ch, and its size in @s.
364 const uint32_t *term_char_resolve(term_char_t ch, size_t *s, term_charbuf_t *b) {
365 static term_charbuf_t static_b;
373 cache = static_b.buf;
375 if (term_char_is_null(ch)) {
378 } else if (term_char_is_allocated(ch)) {
381 cache = c->codepoints;
383 len = char_unpack(ch, &cache[0], &cache[1], &cache[2]);
394 * term_char_lookup_width() - Lookup cell-width of a character
395 * @ch: character to return cell-width for
397 * This is an equivalent of wcwidth() for term_char_t. It can deal directly
398 * with UCS-4 and combining-characters and avoids the mess that is wchar_t and
401 * Returns: 0 for unprintable characters, >0 for everything else.
403 unsigned int term_char_lookup_width(term_char_t ch) {
411 str = term_char_resolve(ch, &len, &b);
413 for (i = 0; i < len; ++i) {
415 * Oh god, C99 locale handling strikes again: wcwidth() expects
416 * wchar_t, but there is no way for us to know the
417 * internal encoding of wchar_t. Moreover, it is nearly
418 * impossible to convert UCS-4 into wchar_t (except for iconv,
419 * which is way too much overhead).
420 * Therefore, we use our own copy of wcwidth(). Lets just hope
421 * that glibc will one day export it's internal UCS-4 and UTF-8
422 * helpers for direct use.
424 assert_cc(sizeof(wchar_t) >= 4);
425 r = mk_wcwidth((wchar_t)str[i]);
426 if (r > 0 && (unsigned int)r > max)
434 * term_cell_init() - Initialize a new cell
435 * @cell: cell to initialize
436 * @ch: character to set on the cell or TERM_CHAR_NULL
437 * @cwidth: character width of @ch
438 * @attr: attributes to set on the cell or NULL
439 * @age: age to set on the cell or TERM_AGE_NULL
441 * This initializes a new cell. The backing-memory of the cell must be allocated
442 * by the caller beforehand. The caller is responsible to destroy the cell via
443 * term_cell_destroy() before freeing the backing-memory.
445 * It is safe (and supported!) to use:
448 * term_cell_init(c, TERM_CHAR_NULL, NULL, TERM_AGE_NULL);
450 * Note that this call takes ownership of @ch. If you want to use it yourself
451 * after this call, you need to duplicate it before calling this.
453 static void term_cell_init(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
457 cell->cwidth = cwidth;
461 memcpy(&cell->attr, attr, sizeof(*attr));
467 * term_cell_destroy() - Destroy previously initialized cell
468 * @cell: cell to destroy or NULL
470 * This releases all resources associated with a cell. The backing memory is
471 * kept as-is. It's the responsibility of the caller to manage it.
473 * You must not call any other cell operations on this cell after this call
474 * returns. You must re-initialize the cell via term_cell_init() before you can
477 * If @cell is NULL, this is a no-op.
479 static void term_cell_destroy(term_cell *cell) {
483 term_char_free(cell->ch);
487 * term_cell_set() - Change contents of a cell
488 * @cell: cell to modify
489 * @ch: character to set on the cell or cell->ch
490 * @cwidth: character width of @ch or cell->cwidth
491 * @attr: attributes to set on the cell or NULL
492 * @age: age to set on the cell or cell->age
494 * This changes the contents of a cell. It can be used to change the character,
495 * attributes and age. To keep the current character, pass cell->ch as @ch. To
496 * reset the current attributes, pass NULL. To keep the current age, pass
499 * This call takes ownership of @ch. You need to duplicate it first, in case you
500 * want to use it for your own purposes after this call.
502 * The cell must have been initialized properly before calling this. See
505 static void term_cell_set(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
508 if (!term_char_same(ch, cell->ch)) {
509 term_char_free(cell->ch);
513 cell->cwidth = cwidth;
517 memcpy(&cell->attr, attr, sizeof(*attr));
523 * term_cell_append() - Append a combining-char to a cell
524 * @cell: cell to modify
525 * @ucs4: UCS-4 character to append to the cell
526 * @age: new age to set on the cell or cell->age
528 * This appends a combining-character to a cell. No validation of the UCS-4
529 * character is done, so this can be used to append any character. Additionally,
530 * this can update the age of the cell.
532 * The cell must have been initialized properly before calling this. See
535 static void term_cell_append(term_cell *cell, uint32_t ucs4, term_age_t age) {
538 cell->ch = term_char_merge(cell->ch, ucs4);
543 * term_cell_init_n() - Initialize an array of cells
544 * @cells: pointer to an array of cells to initialize
545 * @n: number of cells
546 * @attr: attributes to set on all cells or NULL
547 * @age: age to set on all cells
549 * This is the same as term_cell_init() but initializes an array of cells.
550 * Furthermore, this always sets the character to TERM_CHAR_NULL.
551 * If you want to set a specific characters on all cells, you need to hard-code
552 * this loop and duplicate the character for each cell.
554 static void term_cell_init_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) {
555 for ( ; n > 0; --n, ++cells)
556 term_cell_init(cells, TERM_CHAR_NULL, 0, attr, age);
560 * term_cell_destroy_n() - Destroy an array of cells
561 * @cells: pointer to an array of cells to destroy
562 * @n: number of cells
564 * This is the same as term_cell_destroy() but destroys an array of cells.
566 static void term_cell_destroy_n(term_cell *cells, unsigned int n) {
567 for ( ; n > 0; --n, ++cells)
568 term_cell_destroy(cells);
572 * term_cell_clear_n() - Clear contents of an array of cells
573 * @cells: pointer to an array of cells to modify
574 * @n: number of cells
575 * @attr: attributes to set on all cells or NULL
576 * @age: age to set on all cells
578 * This is the same as term_cell_set() but operates on an array of cells. Note
579 * that all characters are always set to TERM_CHAR_NULL, unlike term_cell_set()
580 * which takes the character as argument.
581 * If you want to set a specific characters on all cells, you need to hard-code
582 * this loop and duplicate the character for each cell.
584 static void term_cell_clear_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) {
585 for ( ; n > 0; --n, ++cells)
586 term_cell_set(cells, TERM_CHAR_NULL, 0, attr, age);
590 * term_line_new() - Allocate a new line
591 * @out: place to store pointer to new line
593 * This allocates and initialized a new line. The line is unlinked and
594 * independent of any page. It can be used for any purpose. The initial
595 * cell-count is set to 0.
597 * The line has to be freed via term_line_free() once it's no longer needed.
599 * Returns: 0 on success, negative error code on failure.
601 int term_line_new(term_line **out) {
602 _term_line_free_ term_line *line = NULL;
604 assert_return(out, -EINVAL);
606 line = new0(term_line, 1);
616 * term_line_free() - Free a line
617 * @line: line to free or NULL
619 * This frees a line that was previously allocated via term_line_free(). All its
620 * cells are released, too.
622 * If @line is NULL, this is a no-op.
624 term_line *term_line_free(term_line *line) {
628 term_cell_destroy_n(line->cells, line->n_cells);
636 * term_line_reserve() - Pre-allocate cells for a line
637 * @line: line to pre-allocate cells for
638 * @width: numbers of cells the line shall have pre-allocated
639 * @attr: attribute for all allocated cells or NULL
640 * @age: current age for all modifications
641 * @protect_width: width to protect from erasure
643 * This pre-allocates cells for this line. Please note that @width is the number
644 * of cells the line is guaranteed to have allocated after this call returns.
645 * It's not the number of cells that are added, neither is it the new width of
648 * This function never frees memory. That is, reducing the line-width will
649 * always succeed, same is true for increasing the width to a previously set
652 * @attr and @age are used to initialize new cells. Additionally, any
653 * existing cell outside of the protected area specified by @protect_width are
654 * cleared and reset with @attr and @age.
656 * Returns: 0 on success, negative error code on failure.
658 int term_line_reserve(term_line *line, unsigned int width, const term_attr *attr, term_age_t age, unsigned int protect_width) {
659 unsigned int min_width;
662 assert_return(line, -EINVAL);
664 /* reset existing cells if required */
665 min_width = MIN(line->n_cells, width);
666 if (min_width > protect_width)
667 term_cell_clear_n(line->cells + protect_width,
668 min_width - protect_width,
672 /* allocate new cells if required */
674 if (width > line->n_cells) {
675 t = realloc_multiply(line->cells, sizeof(*t), width);
680 memzero(t + line->n_cells,
681 sizeof(*t) * (width - line->n_cells));
683 term_cell_init_n(t + line->n_cells,
684 width - line->n_cells,
689 line->n_cells = width;
692 line->fill = MIN(line->fill, protect_width);
698 * term_line_set_width() - Change width of a line
699 * @line: line to modify
702 * This changes the actual width of a line. It is the caller's responsibility
703 * to use term_line_reserve() to make sure enough space is allocated. If @width
704 * is greater than the allocated size, it is cropped.
706 * This does not modify any cells. Use term_line_reserve() or term_line_erase()
707 * to clear any newly added cells.
709 * NOTE: The fill state is cropped at line->width. Therefore, if you increase
710 * the line-width afterwards, but there is a multi-cell character at the
711 * end of the line that got cropped, then the fill-state will _not_ be
713 * This means, the fill-state always includes the cells up to the start
714 * of the right-most character, but it might or might not cover it until
715 * its end. This should be totally fine, though. You should never access
716 * multi-cell tails directly, anyway.
718 void term_line_set_width(term_line *line, unsigned int width) {
721 if (width > line->n_cells)
722 width = line->n_cells;
725 line->fill = MIN(line->fill, width);
729 * line_insert() - Insert characters and move existing cells to the right
730 * @from: position to insert cells at
731 * @num: number of cells to insert
732 * @head_char: character that is set on the first cell
733 * @head_cwidth: character-length of @head_char
734 * @attr: attribute for all inserted cells or NULL
735 * @age: current age for all modifications
737 * The INSERT operation (or writes with INSERT_MODE) writes data at a specific
738 * position on a line and shifts the existing cells to the right. Cells that are
739 * moved beyond the right hand border are discarded.
741 * This helper contains the actual INSERT implementation which is independent of
742 * the data written. It works on cells, not on characters. The first cell is set
743 * to @head_char, all others are reset to TERM_CHAR_NULL. See each caller for a
744 * more detailed description.
746 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) {
747 unsigned int i, rem, move;
749 if (from >= line->width)
751 if (from + num < from || from + num > line->width)
752 num = line->width - from;
756 move = line->width - from - num;
757 rem = MIN(num, move);
761 * Make room for @num cells; shift cells to the right if
762 * required. @rem is the number of remaining cells that we will
763 * knock off on the right and overwrite during the right shift.
765 * For INSERT_MODE, @num/@rem are usually 1 or 2, @move is 50%
766 * of the line on average. Therefore, the actual move is quite
767 * heavy and we can safely invalidate cells manually instead of
769 * However, for INSERT operations, any parameters are
770 * possible. But we cannot place any assumption on its usage
771 * across applications, so we just handle it the same as
772 * INSERT_MODE and do per-cell invalidation.
775 /* destroy cells that are knocked off on the right */
776 term_cell_destroy_n(line->cells + line->width - rem, rem);
778 /* move remaining bulk of cells */
779 memmove(line->cells + from + num,
781 sizeof(*line->cells) * move);
783 /* invalidate cells */
784 for (i = 0; i < move; ++i)
785 line->cells[from + num + i].age = age;
787 /* initialize fresh head-cell */
788 term_cell_init(line->cells + from,
794 /* initialize fresh tail-cells */
795 term_cell_init_n(line->cells + from + 1,
800 /* adjust fill-state */
801 DISABLE_WARNING_SHADOW;
802 line->fill = MIN(line->width,
803 MAX(line->fill + num,
807 /* modify head-cell */
808 term_cell_set(line->cells + from,
814 /* reset tail-cells */
815 term_cell_clear_n(line->cells + from + 1,
820 /* adjust fill-state */
821 line->fill = line->width;
826 * term_line_write() - Write to a single, specific cell
827 * @line: line to write to
828 * @pos_x: x-position of cell in @line to write to
829 * @ch: character to write to the cell
830 * @cwidth: character width of @ch
831 * @attr: attributes to set on the cell or NULL
832 * @age: current age for all modifications
833 * @insert_mode: true if INSERT-MODE is enabled
835 * This writes to a specific cell in a line. The cell is addressed by its
836 * X-position @pos_x. If that cell does not exist, this is a no-op.
838 * @ch and @attr are set on this cell.
840 * If @insert_mode is true, this inserts the character instead of overwriting
841 * existing data (existing data is now moved to the right before writing).
843 * This function is the low-level handler of normal writes to a terminal.
845 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) {
850 if (pos_x >= line->width)
853 len = MAX(1U, cwidth);
854 if (pos_x + len < pos_x || pos_x + len > line->width)
855 len = line->width - pos_x;
860 /* Use line_insert() to insert the character-head and fill
861 * the remains with NULLs. */
862 line_insert(line, pos_x, len, ch, cwidth, attr, age);
864 /* modify head-cell */
865 term_cell_set(line->cells + pos_x, ch, cwidth, attr, age);
867 /* reset tail-cells */
868 term_cell_clear_n(line->cells + pos_x + 1,
873 /* adjust fill-state */
874 DISABLE_WARNING_SHADOW;
875 line->fill = MIN(line->width,
883 * term_line_insert() - Insert empty cells
884 * @line: line to insert empty cells into
885 * @from: x-position where to insert cells
886 * @num: number of cells to insert
887 * @attr: attributes to set on the cells or NULL
888 * @age: current age for all modifications
890 * This inserts @num empty cells at position @from in line @line. All existing
891 * cells to the right are shifted to make room for the new cells. Cells that get
892 * pushed beyond the right hand border are discarded.
894 void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) {
895 /* use line_insert() to insert @num empty cells */
896 return line_insert(line, from, num, TERM_CHAR_NULL, 0, attr, age);
900 * term_line_delete() - Delete cells from line
901 * @line: line to delete cells from
902 * @from: position to delete cells at
903 * @num: number of cells to delete
904 * @attr: attributes to set on any new cells
905 * @age: current age for all modifications
907 * Delete cells from a line. All cells to the right of the deleted cells are
908 * shifted to the left to fill the empty space. New cells appearing on the right
909 * hand border are cleared and initialized with @attr.
911 void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) {
912 unsigned int rem, move, i;
916 if (from >= line->width)
918 if (from + num < from || from + num > line->width)
919 num = line->width - from;
923 /* destroy and move as many upfront as possible */
924 move = line->width - from - num;
925 rem = MIN(num, move);
927 /* destroy to be removed cells */
928 term_cell_destroy_n(line->cells + from, rem);
930 /* move tail upfront */
931 memmove(line->cells + from,
932 line->cells + from + num,
933 sizeof(*line->cells) * move);
935 /* invalidate copied cells */
936 for (i = 0; i < move; ++i)
937 line->cells[from + i].age = age;
939 /* initialize tail that was moved away */
940 term_cell_init_n(line->cells + line->width - rem,
945 /* reset remaining cells in case the move was too small */
947 term_cell_clear_n(line->cells + from + move,
953 term_cell_clear_n(line->cells + from,
959 /* adjust fill-state */
960 if (from + num < line->fill)
962 else if (from < line->fill)
967 * term_line_append_combchar() - Append combining char to existing cell
968 * @line: line to modify
969 * @pos_x: position of cell to append combining char to
970 * @ucs4: combining character to append
971 * @age: current age for all modifications
973 * Unicode allows trailing combining characters, which belong to the
974 * char in front of them. The caller is responsible of detecting
975 * combining characters and calling term_line_append_combchar() instead of
976 * term_line_write(). This simply appends the char to the correct cell then.
977 * If the cell is not in the visible area, this call is skipped.
979 * Note that control-sequences are not 100% compatible with combining
980 * characters as they require delayed parsing. However, we must handle
981 * control-sequences immediately. Therefore, there might be trailing
982 * combining chars that should be discarded by the parser.
983 * However, to prevent programming errors, we're also being pedantic
984 * here and discard weirdly placed combining chars. This prevents
985 * situations were invalid content is parsed into the terminal and you
986 * might end up with cells containing only combining chars.
988 * Long story short: To get combining-characters working with old-fashioned
989 * terminal-emulation, we parse them exclusively for direct cell-writes. Other
990 * combining-characters are usually simply discarded and ignored.
992 void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age) {
995 if (pos_x >= line->width)
998 /* Unused cell? Skip appending any combining chars then. */
999 if (term_char_is_null(line->cells[pos_x].ch))
1002 term_cell_append(line->cells + pos_x, ucs4, age);
1006 * term_line_erase() - Erase parts of a line
1007 * @line: line to modify
1008 * @from: position to start the erase
1009 * @num: number of cells to erase
1010 * @attr: attributes to initialize erased cells with
1011 * @age: current age for all modifications
1012 * @keep_protected: true if protected cells should be kept
1014 * This is the standard erase operation. It clears all cells in the targeted
1015 * area and re-initializes them. Cells to the right are not shifted left, you
1016 * must use DELETE to achieve that. Cells outside the visible area are skipped.
1018 * If @keep_protected is true, protected cells will not be erased.
1020 void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected) {
1022 unsigned int i, last_protected;
1026 if (from >= line->width)
1028 if (from + num < from || from + num > line->width)
1029 num = line->width - from;
1034 for (i = 0; i < num; ++i) {
1035 cell = line->cells + from + i;
1036 if (keep_protected && cell->attr.protect) {
1037 /* only count protected-cells inside the fill-region */
1038 if (from + i < line->fill)
1039 last_protected = from + i;
1044 term_cell_set(cell, TERM_CHAR_NULL, 0, attr, age);
1047 /* Adjust fill-state. This is a bit tricks, we can only adjust it in
1048 * case the erase-region starts inside the fill-region and ends at the
1049 * tail or beyond the fill-region. Otherwise, the current fill-state
1051 * Furthermore, we must account for protected cells. The loop above
1052 * ensures that protected-cells are only accounted for if they're
1053 * inside the fill-region. */
1054 if (from < line->fill && from + num >= line->fill)
1055 line->fill = MAX(from, last_protected);
1059 * term_line_reset() - Reset a line
1060 * @line: line to reset
1061 * @attr: attributes to initialize all cells with
1062 * @age: current age for all modifications
1064 * This resets all visible cells of a line and sets their attributes and ages
1065 * to @attr and @age. This is equivalent to erasing a whole line via
1066 * term_line_erase().
1068 void term_line_reset(term_line *line, const term_attr *attr, term_age_t age) {
1071 return term_line_erase(line, 0, line->width, attr, age, 0);
1075 * term_line_link() - Link line in front of a list
1076 * @line: line to link
1077 * @first: member pointing to first entry
1078 * @last: member pointing to last entry
1080 * This links a line into a list of lines. The line is inserted at the front and
1081 * must not be linked, yet. See the TERM_LINE_LINK() macro for an easier usage of
1084 void term_line_link(term_line *line, term_line **first, term_line **last) {
1088 assert(!line->lines_prev);
1089 assert(!line->lines_next);
1091 line->lines_prev = NULL;
1092 line->lines_next = *first;
1094 (*first)->lines_prev = line;
1101 * term_line_link_tail() - Link line at tail of a list
1102 * @line: line to link
1103 * @first: member pointing to first entry
1104 * @last: member pointing to last entry
1106 * Same as term_line_link() but links the line at the tail.
1108 void term_line_link_tail(term_line *line, term_line **first, term_line **last) {
1112 assert(!line->lines_prev);
1113 assert(!line->lines_next);
1115 line->lines_next = NULL;
1116 line->lines_prev = *last;
1118 (*last)->lines_next = line;
1125 * term_line_unlink() - Unlink line from a list
1126 * @line: line to unlink
1127 * @first: member pointing to first entry
1128 * @last: member pointing to last entry
1130 * This unlinks a previously linked line. See TERM_LINE_UNLINK() for an easier to
1133 void term_line_unlink(term_line *line, term_line **first, term_line **last) {
1138 if (line->lines_prev)
1139 line->lines_prev->lines_next = line->lines_next;
1141 *first = line->lines_next;
1142 if (line->lines_next)
1143 line->lines_next->lines_prev = line->lines_prev;
1145 *last = line->lines_prev;
1147 line->lines_prev = NULL;
1148 line->lines_next = NULL;
1152 * term_page_new() - Allocate new page
1153 * @out: storage for pointer to new page
1155 * Allocate a new page. The initial dimensions are 0/0.
1157 * Returns: 0 on success, negative error code on failure.
1159 int term_page_new(term_page **out) {
1160 _term_page_free_ term_page *page = NULL;
1162 assert_return(out, -EINVAL);
1164 page = new0(term_page, 1);
1174 * term_page_free() - Free page
1175 * @page: page to free or NULL
1177 * Free a previously allocated page and all associated data. If @page is NULL,
1182 term_page *term_page_free(term_page *page) {
1188 for (i = 0; i < page->n_lines; ++i)
1189 term_line_free(page->lines[i]);
1191 free(page->line_cache);
1199 * term_page_get_cell() - Return pointer to requested cell
1200 * @page: page to operate on
1201 * @x: x-position of cell
1202 * @y: y-position of cell
1204 * This returns a pointer to the cell at position @x/@y. You're free to modify
1205 * this cell as much as you like. However, once you call any other function on
1206 * the page, you must drop the pointer to the cell.
1208 * Returns: Pointer to the cell or NULL if out of the visible area.
1210 term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y) {
1211 assert_return(page, NULL);
1213 if (x >= page->width)
1215 if (y >= page->height)
1218 return &page->lines[y]->cells[x];
1222 * page_scroll_up() - Scroll up
1223 * @page: page to operate on
1224 * @new_width: width to use for any new line moved into the visible area
1225 * @num: number of lines to scroll up
1226 * @attr: attributes to set on new lines
1227 * @age: age to use for all modifications
1228 * @history: history to use for old lines or NULL
1230 * This scrolls the scroll-region by @num lines. New lines are cleared and reset
1231 * with the given attributes. Old lines are moved into the history if non-NULL.
1232 * If a new line is allocated, moved from the history buffer or moved from
1233 * outside the visible region into the visible region, this call makes sure it
1234 * has at least @width cells allocated. If a possible memory-allocation fails,
1235 * the previous line is reused. This has the side effect, that it will not be
1236 * linked into the history buffer.
1238 * If the scroll-region is empty, this is a no-op.
1240 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) {
1241 term_line *line, **cache;
1247 if (num > page->scroll_num)
1248 num = page->scroll_num;
1252 /* Better safe than sorry: avoid under-allocating lines, even when
1254 new_width = MAX(new_width, page->width);
1256 cache = page->line_cache;
1258 /* Try moving lines into history and allocate new lines for each moved
1259 * line. In case allocation fails, or if we have no history, reuse the
1261 * We keep the lines in the line-cache so we can safely move the
1262 * remaining lines around. */
1263 for (i = 0; i < num; ++i) {
1264 line = page->lines[page->scroll_idx + i];
1268 r = term_line_new(&cache[i]);
1270 r = term_line_reserve(cache[i],
1276 term_line_free(cache[i]);
1278 term_line_set_width(cache[i], page->width);
1283 term_history_push(history, line);
1286 term_line_reset(line, attr, age);
1290 if (num < page->scroll_num) {
1291 memmove(page->lines + page->scroll_idx,
1292 page->lines + page->scroll_idx + num,
1293 sizeof(*page->lines) * (page->scroll_num - num));
1295 /* update age of moved lines */
1296 for (i = 0; i < page->scroll_num - num; ++i)
1297 page->lines[page->scroll_idx + i]->age = age;
1300 /* copy remaining lines from cache; age is already updated */
1301 memcpy(page->lines + page->scroll_idx + page->scroll_num - num,
1303 sizeof(*cache) * num);
1306 page->scroll_fill -= MIN(page->scroll_fill, num);
1310 * page_scroll_down() - Scroll down
1311 * @page: page to operate on
1312 * @new_width: width to use for any new line moved into the visible area
1313 * @num: number of lines to scroll down
1314 * @attr: attributes to set on new lines
1315 * @age: age to use for all modifications
1316 * @history: history to use for new lines or NULL
1318 * This scrolls the scroll-region by @num lines. New lines are retrieved from
1319 * the history or cleared if the history is empty or NULL.
1321 * Usually, scroll-down implies that new lines are cleared. Therefore, you're
1322 * highly encouraged to set @history to NULL. However, if you resize a terminal,
1323 * you might want to include history-lines in the new area. In that case, you
1324 * should set @history to non-NULL.
1326 * If a new line is allocated, moved from the history buffer or moved from
1327 * outside the visible region into the visible region, this call makes sure it
1328 * has at least @width cells allocated. If a possible memory-allocation fails,
1329 * the previous line is reused. This will have the side-effect that lines from
1330 * the history will not get visible on-screen but kept in history.
1332 * If the scroll-region is empty, this is a no-op.
1334 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) {
1335 term_line *line, **cache, *t;
1336 unsigned int i, last_idx;
1340 if (num > page->scroll_num)
1341 num = page->scroll_num;
1345 /* Better safe than sorry: avoid under-allocating lines, even when
1347 new_width = MAX(new_width, page->width);
1349 cache = page->line_cache;
1350 last_idx = page->scroll_idx + page->scroll_num - 1;
1352 /* Try pulling out lines from history; if history is empty or if no
1353 * history is given, we reuse the to-be-removed lines. Otherwise, those
1354 * lines are released. */
1355 for (i = 0; i < num; ++i) {
1356 line = page->lines[last_idx - i];
1360 t = term_history_pop(history, new_width, attr, age);
1363 cache[num - 1 - i] = t;
1364 term_line_free(line);
1366 cache[num - 1 - i] = line;
1367 term_line_reset(line, attr, age);
1371 if (num < page->scroll_num) {
1372 memmove(page->lines + page->scroll_idx + num,
1373 page->lines + page->scroll_idx,
1374 sizeof(*page->lines) * (page->scroll_num - num));
1376 /* update age of moved lines */
1377 for (i = 0; i < page->scroll_num - num; ++i)
1378 page->lines[page->scroll_idx + num + i]->age = age;
1381 /* copy remaining lines from cache; age is already updated */
1382 memcpy(page->lines + page->scroll_idx,
1384 sizeof(*cache) * num);
1386 /* update fill; but only if there's already content in it */
1387 if (page->scroll_fill > 0)
1388 page->scroll_fill = MIN(page->scroll_num,
1389 page->scroll_fill + num);
1393 * page_reserve() - Reserve page area
1394 * @page: page to modify
1395 * @cols: required columns (width)
1396 * @rows: required rows (height)
1397 * @attr: attributes for newly allocated cells
1398 * @age: age to set on any modified cells
1400 * This allocates the required amount of lines and cells to guarantee that the
1401 * page has at least the demanded dimensions of @cols x @rows. Note that this
1402 * never shrinks the page-memory. We keep cells allocated for performance
1405 * Additionally to allocating lines, this also clears any newly added cells so
1406 * you can safely change the size afterwards without clearing new cells.
1408 * Note that you must be careful what operations you call on the page between
1409 * page_reserve() and updating page->width/height. Any newly allocated line (or
1410 * shifted line) might not meet your new width/height expectations.
1412 * Returns: 0 on success, negative error code on failure.
1414 int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age) {
1415 _term_line_free_ term_line *line = NULL;
1416 unsigned int i, min_lines;
1420 assert_return(page, -EINVAL);
1423 * First make sure the first MIN(page->n_lines, rows) lines have at
1424 * least the required width of @cols. This does not modify any visible
1425 * cells in the existing @page->width x @page->height area, therefore,
1426 * we can safely bail out afterwards in case anything else fails.
1427 * Note that lines in between page->height and page->n_lines might be
1428 * shorter than page->width. Hence, we need to resize them all, but we
1429 * can skip some of them for better performance.
1431 min_lines = MIN(page->n_lines, rows);
1432 for (i = 0; i < min_lines; ++i) {
1433 /* lines below page->height have at least page->width cells */
1434 if (cols < page->width && i < page->height)
1437 r = term_line_reserve(page->lines[i],
1441 (i < page->height) ? page->width : 0);
1447 * We now know the first @min_lines lines have at least width @cols and
1448 * are prepared for resizing. We now only have to allocate any
1449 * additional lines below @min_lines in case @rows is greater than
1452 if (rows > page->n_lines) {
1453 t = realloc_multiply(page->lines, sizeof(*t), rows);
1458 t = realloc_multiply(page->line_cache, sizeof(*t), rows);
1461 page->line_cache = t;
1463 while (page->n_lines < rows) {
1464 r = term_line_new(&line);
1468 r = term_line_reserve(line, cols, attr, age, 0);
1472 page->lines[page->n_lines++] = line;
1481 * term_page_resize() - Resize page
1482 * @page: page to modify
1483 * @cols: number of columns (width)
1484 * @rows: number of rows (height)
1485 * @attr: attributes for newly allocated cells
1486 * @age: age to set on any modified cells
1487 * @history: history buffer to use for new/old lines or NULL
1489 * This changes the visible dimensions of a page. You must have called
1490 * term_page_reserve() beforehand, otherwise, this will fail.
1492 * Returns: 0 on success, negative error code on failure.
1494 void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history) {
1495 unsigned int i, num, empty, max, old_height;
1499 assert(page->n_lines >= rows);
1501 old_height = page->height;
1503 if (rows < old_height) {
1505 * If we decrease the terminal-height, we emulate a scroll-up.
1506 * This way, existing data from the scroll-area is moved into
1507 * the history, making space at the bottom to reduce the screen
1508 * height. In case the scroll-fill indicates empty lines, we
1509 * reduce the amount of scrolled lines.
1510 * Once scrolled, we have to move the lower margin from below
1511 * the scroll area up so it is preserved.
1514 /* move lines to history if scroll region is filled */
1515 num = old_height - rows;
1516 empty = page->scroll_num - page->scroll_fill;
1518 page_scroll_up(page,
1525 /* move lower margin up; drop its lines if not enough space */
1526 num = LESS_BY(old_height, page->scroll_idx + page->scroll_num);
1527 max = LESS_BY(rows, page->scroll_idx);
1528 num = MIN(num, max);
1530 unsigned int top, bottom;
1533 bottom = page->scroll_idx + page->scroll_num;
1535 /* might overlap; must run topdown, not bottomup */
1536 for (i = 0; i < num; ++i) {
1537 line = page->lines[top + i];
1538 page->lines[top + i] = page->lines[bottom + i];
1539 page->lines[bottom + i] = line;
1543 /* update vertical extents */
1544 page->height = rows;
1545 page->scroll_idx = MIN(page->scroll_idx, rows);
1546 page->scroll_num -= MIN(page->scroll_num, old_height - rows);
1547 /* fill is already up-to-date or 0 due to scroll-up */
1548 } else if (rows > old_height) {
1550 * If we increase the terminal-height, we emulate a scroll-down
1551 * and fetch new lines from the history.
1552 * New lines are always accounted to the scroll-region. Thus we
1553 * have to preserve the lower margin first, by moving it down.
1556 /* move lower margin down */
1557 num = LESS_BY(old_height, page->scroll_idx + page->scroll_num);
1559 unsigned int top, bottom;
1561 top = page->scroll_idx + page->scroll_num;
1562 bottom = top + (rows - old_height);
1564 /* might overlap; must run bottomup, not topdown */
1565 for (i = num; i-- > 0; ) {
1566 line = page->lines[top + i];
1567 page->lines[top + i] = page->lines[bottom + i];
1568 page->lines[bottom + i] = line;
1572 /* update vertical extents */
1573 page->height = rows;
1574 page->scroll_num = MIN(LESS_BY(rows, page->scroll_idx),
1575 page->scroll_num + (rows - old_height));
1577 /* check how many lines can be received from history */
1579 num = term_history_peek(history,
1587 /* retrieve new lines from history if available */
1589 page_scroll_down(page,
1597 /* set horizontal extents */
1599 for (i = 0; i < page->height; ++i)
1600 term_line_set_width(page->lines[i], cols);
1604 * term_page_write() - Write to a single cell
1605 * @page: page to operate on
1606 * @pos_x: x-position of cell to write to
1607 * @pos_y: y-position of cell to write to
1608 * @ch: character to write
1609 * @cwidth: character-width of @ch
1610 * @attr: attributes to set on the cell or NULL
1611 * @age: age to use for all modifications
1612 * @insert_mode: true if INSERT-MODE is enabled
1614 * This writes a character to a specific cell. If the cell is beyond bounds,
1615 * this is a no-op. @attr and @age are used to update the cell. @flags can be
1616 * used to alter the behavior of this function.
1618 * This is a wrapper around term_line_write().
1620 * This call does not wrap around lines. That is, this only operates on a single
1623 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) {
1626 if (pos_y >= page->height)
1629 term_line_write(page->lines[pos_y], pos_x, ch, cwidth, attr, age, insert_mode);
1633 * term_page_insert_cells() - Insert cells into a line
1634 * @page: page to operate on
1635 * @from_x: x-position where to insert new cells
1636 * @from_y: y-position where to insert new cells
1637 * @num: number of cells to insert
1638 * @attr: attributes to set on new cells or NULL
1639 * @age: age to use for all modifications
1641 * This inserts new cells into a given line. This is a wrapper around
1642 * term_line_insert().
1644 * This call does not wrap around lines. That is, this only operates on a single
1647 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) {
1650 if (from_y >= page->height)
1653 term_line_insert(page->lines[from_y], from_x, num, attr, age);
1657 * term_page_delete_cells() - Delete cells from a line
1658 * @page: page to operate on
1659 * @from_x: x-position where to delete cells
1660 * @from_y: y-position where to delete cells
1661 * @num: number of cells to delete
1662 * @attr: attributes to set on new cells or NULL
1663 * @age: age to use for all modifications
1665 * This deletes cells from a given line. This is a wrapper around
1666 * term_line_delete().
1668 * This call does not wrap around lines. That is, this only operates on a single
1671 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) {
1674 if (from_y >= page->height)
1677 term_line_delete(page->lines[from_y], from_x, num, attr, age);
1681 * term_page_append_combchar() - Append combining-character to a cell
1682 * @page: page to operate on
1683 * @pos_x: x-position of target cell
1684 * @pos_y: y-position of target cell
1685 * @ucs4: combining character to append
1686 * @age: age to use for all modifications
1688 * This appends a combining-character to a specific cell. This is a wrapper
1689 * around term_line_append_combchar().
1691 void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age) {
1694 if (pos_y >= page->height)
1697 term_line_append_combchar(page->lines[pos_y], pos_x, ucs4, age);
1701 * term_page_erase() - Erase parts of a page
1702 * @page: page to operate on
1703 * @from_x: x-position where to start erasure (inclusive)
1704 * @from_y: y-position where to start erasure (inclusive)
1705 * @to_x: x-position where to stop erasure (inclusive)
1706 * @to_y: y-position where to stop erasure (inclusive)
1707 * @attr: attributes to set on cells
1708 * @age: age to use for all modifications
1709 * @keep_protected: true if protected cells should be kept
1711 * This erases all cells starting at @from_x/@from_y up to @to_x/@to_y. Note
1712 * that this wraps around line-boundaries so lines between @from_y and @to_y
1713 * are cleared entirely.
1715 * Lines outside the visible area are left untouched.
1717 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) {
1718 unsigned int i, from, to;
1722 for (i = from_y; i <= to_y && i < page->height; ++i) {
1731 term_line_erase(page->lines[i],
1741 * term_page_reset() - Reset page
1742 * @page: page to modify
1743 * @attr: attributes to set on cells
1744 * @age: age to use for all modifications
1746 * This erases the whole visible page. See term_page_erase().
1748 void term_page_reset(term_page *page, const term_attr *attr, term_age_t age) {
1751 return term_page_erase(page,
1753 page->width - 1, page->height - 1,
1760 * term_page_set_scroll_region() - Set scroll region
1761 * @page: page to operate on
1762 * @idx: start-index of scroll region
1763 * @num: number of lines in scroll region
1765 * This sets the scroll region of a page. Whenever an operation needs to scroll
1766 * lines, it scrolls them inside of that region. Lines outside the region are
1767 * left untouched. In case a scroll-operation is targeted outside of this
1768 * region, it will implicitly get a scroll-region of only one line (i.e., no
1769 * scroll region at all).
1771 * Note that the scroll-region is clipped to the current page-extents. Growing
1772 * or shrinking the page always accounts new/old lines to the scroll region and
1773 * moves top/bottom margins accordingly so they're preserved.
1775 void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num) {
1778 if (page->height < 1) {
1779 page->scroll_idx = 0;
1780 page->scroll_num = 0;
1782 page->scroll_idx = MIN(idx, page->height - 1);
1783 page->scroll_num = MIN(num, page->height - page->scroll_idx);
1788 * term_page_scroll_up() - Scroll up
1789 * @page: page to operate on
1790 * @num: number of lines to scroll up
1791 * @attr: attributes to set on new lines
1792 * @age: age to use for all modifications
1793 * @history: history to use for old lines or NULL
1795 * This scrolls the scroll-region by @num lines. New lines are cleared and reset
1796 * with the given attributes. Old lines are moved into the history if non-NULL.
1798 * If the scroll-region is empty, this is a no-op.
1800 void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1801 page_scroll_up(page, page->width, num, attr, age, history);
1805 * term_page_scroll_down() - Scroll down
1806 * @page: page to operate on
1807 * @num: number of lines to scroll down
1808 * @attr: attributes to set on new lines
1809 * @age: age to use for all modifications
1810 * @history: history to use for new lines or NULL
1812 * This scrolls the scroll-region by @num lines. New lines are retrieved from
1813 * the history or cleared if the history is empty or NULL.
1815 * Usually, scroll-down implies that new lines are cleared. Therefore, you're
1816 * highly encouraged to set @history to NULL. However, if you resize a terminal,
1817 * you might want to include history-lines in the new area. In that case, you
1818 * should set @history to non-NULL.
1820 * If the scroll-region is empty, this is a no-op.
1822 void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1823 page_scroll_down(page, page->width, num, attr, age, history);
1827 * term_page_insert_lines() - Insert new lines
1828 * @page: page to operate on
1829 * @pos_y: y-position where to insert new lines
1830 * @num: number of lines to insert
1831 * @attr: attributes to set on new lines
1832 * @age: age to use for all modifications
1834 * This inserts @num new lines at position @pos_y. If @pos_y is beyond
1835 * boundaries or @num is 0, this is a no-op.
1836 * All lines below @pos_y are moved down to make space for the new lines. Lines
1837 * on the bottom are dropped. Note that this only moves lines above or inside
1838 * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of
1839 * one line is implied (which means the line is simply cleared).
1841 void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) {
1842 unsigned int scroll_idx, scroll_num;
1846 if (pos_y >= page->height)
1848 if (num >= page->height)
1851 /* remember scroll-region */
1852 scroll_idx = page->scroll_idx;
1853 scroll_num = page->scroll_num;
1855 /* set scroll-region temporarily so we can reuse scroll_down() */
1857 page->scroll_idx = pos_y;
1858 if (pos_y >= scroll_idx + scroll_num)
1859 page->scroll_num = 1;
1860 else if (pos_y >= scroll_idx)
1861 page->scroll_num -= pos_y - scroll_idx;
1863 page->scroll_num += scroll_idx - pos_y;
1865 term_page_scroll_down(page, num, attr, age, NULL);
1868 /* reset scroll-region */
1869 page->scroll_idx = scroll_idx;
1870 page->scroll_num = scroll_num;
1874 * term_page_delete_lines() - Delete lines
1875 * @page: page to operate on
1876 * @pos_y: y-position where to delete lines
1877 * @num: number of lines to delete
1878 * @attr: attributes to set on new lines
1879 * @age: age to use for all modifications
1881 * This deletes @num lines at position @pos_y. If @pos_y is beyond boundaries or
1882 * @num is 0, this is a no-op.
1883 * All lines below @pos_y are moved up into the newly made space. New lines
1884 * on the bottom are clear. Note that this only moves lines above or inside
1885 * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of
1886 * one line is implied (which means the line is simply cleared).
1888 void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) {
1889 unsigned int scroll_idx, scroll_num;
1893 if (pos_y >= page->height)
1895 if (num >= page->height)
1898 /* remember scroll-region */
1899 scroll_idx = page->scroll_idx;
1900 scroll_num = page->scroll_num;
1902 /* set scroll-region temporarily so we can reuse scroll_up() */
1904 page->scroll_idx = pos_y;
1905 if (pos_y >= scroll_idx + scroll_num)
1906 page->scroll_num = 1;
1907 else if (pos_y > scroll_idx)
1908 page->scroll_num -= pos_y - scroll_idx;
1910 page->scroll_num += scroll_idx - pos_y;
1912 term_page_scroll_up(page, num, attr, age, NULL);
1915 /* reset scroll-region */
1916 page->scroll_idx = scroll_idx;
1917 page->scroll_num = scroll_num;
1921 * term_history_new() - Create new history object
1922 * @out: storage for pointer to new history
1924 * Create a new history object. Histories are used to store scrollback-lines
1925 * from VTE pages. You're highly recommended to set a history-limit on
1926 * history->max_lines and trim it via term_history_trim(), otherwise history
1927 * allocations are unlimited.
1929 * Returns: 0 on success, negative error code on failure.
1931 int term_history_new(term_history **out) {
1932 _term_history_free_ term_history *history = NULL;
1934 assert_return(out, -EINVAL);
1936 history = new0(term_history, 1);
1940 history->max_lines = 4096;
1948 * term_history_free() - Free history
1949 * @history: history to free
1951 * Clear and free history. You must not access the object afterwards.
1955 term_history *term_history_free(term_history *history) {
1959 term_history_clear(history);
1965 * term_history_clear() - Clear history
1966 * @history: history to clear
1968 * Remove all linked lines from a history and reset it to its initial state.
1970 void term_history_clear(term_history *history) {
1971 return term_history_trim(history, 0);
1975 * term_history_trim() - Trim history
1976 * @history: history to trim
1977 * @max: maximum number of lines to be left in history
1979 * This removes lines from the history until it is smaller than @max. Lines are
1980 * removed from the top.
1982 void term_history_trim(term_history *history, unsigned int max) {
1988 while (history->n_lines > max && (line = history->lines_first)) {
1989 TERM_LINE_UNLINK(line, history);
1990 term_line_free(line);
1996 * term_history_push() - Push line into history
1997 * @history: history to work on
1998 * @line: line to push into history
2000 * This pushes a line into the given history. It is linked at the tail. In case
2001 * the history is limited, the top-most line might be freed.
2003 void term_history_push(term_history *history, term_line *line) {
2007 TERM_LINE_LINK_TAIL(line, history);
2008 if (history->max_lines > 0 && history->n_lines >= history->max_lines) {
2009 line = history->lines_first;
2010 TERM_LINE_UNLINK(line, history);
2011 term_line_free(line);
2018 * term_history_pop() - Retrieve last line from history
2019 * @history: history to work on
2020 * @new_width: width to reserve and set on the line
2021 * @attr: attributes to use for cell reservation
2022 * @age: age to use for cell reservation
2024 * This unlinks the last linked line of the history and returns it. This also
2025 * makes sure the line has the given width pre-allocated (see
2026 * term_line_reserve()). If the pre-allocation fails, this returns NULL, so it
2027 * is treated like there's no line in history left. This simplifies
2028 * history-handling on the caller's side in case of allocation errors. No need
2029 * to throw lines away just because the reservation failed. We can keep them in
2030 * history safely, and make them available as scrollback.
2032 * Returns: Line from history or NULL
2034 term_line *term_history_pop(term_history *history, unsigned int new_width, const term_attr *attr, term_age_t age) {
2038 assert_return(history, NULL);
2040 line = history->lines_last;
2044 r = term_line_reserve(line, new_width, attr, age, line->width);
2048 term_line_set_width(line, new_width);
2049 TERM_LINE_UNLINK(line, history);
2056 * term_history_peek() - Return number of available history-lines
2057 * @history: history to work on
2058 * @max: maximum number of lines to look at
2059 * @reserve_width: width to reserve on the line
2060 * @attr: attributes to use for cell reservation
2061 * @age: age to use for cell reservation
2063 * This returns the number of available lines in the history given as @history.
2064 * It returns at most @max. For each line that is looked at, the line is
2065 * verified to have at least @reserve_width cells. Valid cells are preserved,
2066 * new cells are initialized with @attr and @age. In case an allocation fails,
2067 * we bail out and return the number of lines that are valid so far.
2069 * Usually, this function should be used before running a loop on
2070 * term_history_pop(). This function guarantees that term_history_pop() (with
2071 * the same arguments) will succeed at least the returned number of times.
2073 * Returns: Number of valid lines that can be received via term_history_pop().
2075 unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age) {
2083 line = history->lines_last;
2085 while (num < max && line) {
2086 r = term_line_reserve(line, reserve_width, attr, age, line->width);
2091 line = line->lines_prev;