]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-terminal/term-page.c
build-sys: only use AM_PATH_LIBGCRYPT macro if it exists
[thirdparty/systemd.git] / src / libsystemd-terminal / term-page.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
7
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.
12
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.
17
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/>.
20 ***/
21
22 /*
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
26 * object.
27 *
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
31 * operations.
32 *
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.
39 *
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.
55 *
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.
65 *
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
70 * line.
71 * A page does not contain any cursor tracking. It only provides the raw
72 * operations to shuffle lines and modify the page.
73 */
74
75 #include <stdbool.h>
76 #include <stdint.h>
77 #include <stdlib.h>
78 #include "macro.h"
79 #include "term-internal.h"
80 #include "util.h"
81
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)
88
89 /* real storage behind "term_char_t" in case it's not packed */
90 typedef struct term_character {
91 uint8_t n;
92 uint32_t codepoints[];
93 } term_character;
94
95 /*
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.
99 */
100 static inline term_char_t char_pack(uint32_t v1, uint32_t v2, uint32_t v3) {
101 uint64_t packed, u1, u2, u3;
102
103 u1 = v1;
104 u2 = v2;
105 u3 = v3;
106
107 packed = 0x01;
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;
111
112 return TERM_CHAR_INIT(packed);
113 }
114
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))
118
119 /*
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).
125 */
126 static inline uint8_t char_unpack(term_char_t packed, uint32_t *out_v1, uint32_t *out_v2, uint32_t *out_v3) {
127 uint32_t v1, v2, v3;
128
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;
132
133 if (out_v1)
134 *out_v1 = v1;
135 if (out_v2)
136 *out_v2 = v2;
137 if (out_v3)
138 *out_v3 = v3;
139
140 return (v1 > CHAR_UCS4_MAX) ? 0 :
141 ((v2 > CHAR_UCS4_MAX) ? 1 :
142 ((v3 > CHAR_UCS4_MAX) ? 2 :
143 3));
144 }
145
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;
149 }
150
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);
154 }
155
156 /*
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.
162 */
163 static term_character *char_alloc(uint8_t n) {
164 term_character *c;
165 int r;
166
167 r = posix_memalign((void**)&c,
168 MAX(sizeof(void*), (size_t)2),
169 sizeof(*c) + sizeof(*c->codepoints) * (n + 1));
170 if (r)
171 return NULL;
172
173 c->n = n;
174 c->codepoints[n] = 0;
175
176 return c;
177 }
178
179 /*
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.
182 */
183 static inline void char_free(term_char_t ch) {
184 if (term_char_is_allocated(ch))
185 free(char_to_ptr(ch));
186 }
187
188 /*
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.
193 */
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;
197 term_character *c;
198 uint32_t buf[3], *t;
199 uint8_t n;
200
201 /* ignore invalid UCS-4 */
202 if (append_ucs4 > CHAR_UCS4_MAX)
203 return base;
204
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]);
210
211 switch (n) {
212 case 0:
213 return char_pack1(append_ucs4);
214 case 1:
215 if (climit < 2)
216 return base;
217
218 return char_pack2(buf[0], append_ucs4);
219 case 2:
220 if (climit < 3)
221 return base;
222
223 return char_pack3(buf[0], buf[1], append_ucs4);
224 default:
225 /* fallthrough */
226 break;
227 }
228
229 /* already fully packed, we need to allocate a new one */
230 t = buf;
231 } else {
232 /* already an allocated type, we need to allocate a new one */
233 c = char_to_ptr(base);
234 t = c->codepoints;
235 n = c->n;
236 }
237
238 /* bail out if soft-limit is reached */
239 if (n >= climit)
240 return base;
241
242 /* allocate new char */
243 c = char_alloc(n + 1);
244 if (!c)
245 return base;
246
247 memcpy(c->codepoints, t, sizeof(*t) * n);
248 c->codepoints[n] = append_ucs4;
249
250 return char_from_ptr(c);
251 }
252
253 /**
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
257 *
258 * This frees all resources in @previous and re-initializes it to @append_ucs4.
259 * The new char is returned.
260 *
261 * Usually, this is used like this:
262 * obj->ch = term_char_set(obj->ch, ucs4);
263 *
264 * Returns: The previous character reset to @append_ucs4.
265 */
266 term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4) {
267 char_free(previous);
268 return char_build(TERM_CHAR_NULL, append_ucs4);
269 }
270
271 /**
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
275 *
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.
279 *
280 * Usually, this is used like this:
281 * obj->ch = term_char_merge(obj->ch, ucs4);
282 *
283 * Returns: The new merged character.
284 */
285 term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4) {
286 term_char_t ch;
287
288 ch = char_build(base, append_ucs4);
289 if (!term_char_same(ch, base))
290 term_char_free(base);
291
292 return ch;
293 }
294
295 /**
296 * term_char_dup() - Duplicate character
297 * @ch: character to duplicate
298 *
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.
304 *
305 * Returns: The duplicated term-character.
306 */
307 term_char_t term_char_dup(term_char_t ch) {
308 term_character *c, *newc;
309
310 if (!term_char_is_allocated(ch))
311 return ch;
312
313 c = char_to_ptr(ch);
314 newc = char_alloc(c->n);
315 if (!newc)
316 return char_pack1(CHAR_UCS4_REPLACEMENT);
317
318 memcpy(newc->codepoints, c->codepoints, sizeof(*c->codepoints) * c->n);
319 return char_from_ptr(newc);
320 }
321
322 /**
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
326 *
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.
331 *
332 * Returns: Newly allocated character with @append_ucs4 appended to @base.
333 */
334 term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4) {
335 term_char_t ch;
336
337 ch = char_build(base, append_ucs4);
338 if (term_char_same(ch, base))
339 ch = term_char_dup(base);
340
341 return ch;
342 }
343
344 /**
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
349 *
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
353 * returned.
354 *
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.
358 *
359 * This never returns NULL. Even if the size is 0, this points to a buffer of at
360 * least a zero-terminator.
361 *
362 * Returns: The UCS-4 string-representation of @ch, and its size in @s.
363 */
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;
366 term_character *c;
367 uint32_t *cache;
368 size_t len;
369
370 if (b)
371 cache = b->buf;
372 else
373 cache = static_b.buf;
374
375 if (term_char_is_null(ch)) {
376 len = 0;
377 cache[0] = 0;
378 } else if (term_char_is_allocated(ch)) {
379 c = char_to_ptr(ch);
380 len = c->n;
381 cache = c->codepoints;
382 } else {
383 len = char_unpack(ch, &cache[0], &cache[1], &cache[2]);
384 cache[len] = 0;
385 }
386
387 if (s)
388 *s = len;
389
390 return cache;
391 }
392
393 /**
394 * term_char_lookup_width() - Lookup cell-width of a character
395 * @ch: character to return cell-width for
396 *
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
399 * locale handling.
400 *
401 * Returns: 0 for unprintable characters, >0 for everything else.
402 */
403 unsigned int term_char_lookup_width(term_char_t ch) {
404 term_charbuf_t b;
405 const uint32_t *str;
406 unsigned int max;
407 size_t i, len;
408 int r;
409
410 max = 0;
411 str = term_char_resolve(ch, &len, &b);
412
413 for (i = 0; i < len; ++i) {
414 /*
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.
423 */
424 assert_cc(sizeof(wchar_t) >= 4);
425 r = mk_wcwidth((wchar_t)str[i]);
426 if (r > 0 && (unsigned int)r > max)
427 max = r;
428 }
429
430 return max;
431 }
432
433 /**
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
440 *
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.
444 *
445 * It is safe (and supported!) to use:
446 * zero(*c);
447 * instead of:
448 * term_cell_init(c, TERM_CHAR_NULL, NULL, TERM_AGE_NULL);
449 *
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.
452 */
453 static void term_cell_init(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
454 assert(cell);
455
456 cell->ch = ch;
457 cell->cwidth = cwidth;
458 cell->age = age;
459
460 if (attr)
461 memcpy(&cell->attr, attr, sizeof(*attr));
462 else
463 zero(cell->attr);
464 }
465
466 /**
467 * term_cell_destroy() - Destroy previously initialized cell
468 * @cell: cell to destroy or NULL
469 *
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.
472 *
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
475 * use it again.
476 *
477 * If @cell is NULL, this is a no-op.
478 */
479 static void term_cell_destroy(term_cell *cell) {
480 if (!cell)
481 return;
482
483 term_char_free(cell->ch);
484 }
485
486 /**
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
493 *
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
497 * cell->age.
498 *
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.
501 *
502 * The cell must have been initialized properly before calling this. See
503 * term_cell_init().
504 */
505 static void term_cell_set(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) {
506 assert(cell);
507
508 if (!term_char_same(ch, cell->ch)) {
509 term_char_free(cell->ch);
510 cell->ch = ch;
511 }
512
513 cell->cwidth = cwidth;
514 cell->age = age;
515
516 if (attr)
517 memcpy(&cell->attr, attr, sizeof(*attr));
518 else
519 zero(cell->attr);
520 }
521
522 /**
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
527 *
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.
531 *
532 * The cell must have been initialized properly before calling this. See
533 * term_cell_init().
534 */
535 static void term_cell_append(term_cell *cell, uint32_t ucs4, term_age_t age) {
536 assert(cell);
537
538 cell->ch = term_char_merge(cell->ch, ucs4);
539 cell->age = age;
540 }
541
542 /**
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
548 *
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.
553 */
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);
557 }
558
559 /**
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
563 *
564 * This is the same as term_cell_destroy() but destroys an array of cells.
565 */
566 static void term_cell_destroy_n(term_cell *cells, unsigned int n) {
567 for ( ; n > 0; --n, ++cells)
568 term_cell_destroy(cells);
569 }
570
571 /**
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
577 *
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.
583 */
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);
587 }
588
589 /**
590 * term_line_new() - Allocate a new line
591 * @out: place to store pointer to new line
592 *
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.
596 *
597 * The line has to be freed via term_line_free() once it's no longer needed.
598 *
599 * Returns: 0 on success, negative error code on failure.
600 */
601 int term_line_new(term_line **out) {
602 _term_line_free_ term_line *line = NULL;
603
604 assert_return(out, -EINVAL);
605
606 line = new0(term_line, 1);
607 if (!line)
608 return -ENOMEM;
609
610 *out = line;
611 line = NULL;
612 return 0;
613 }
614
615 /**
616 * term_line_free() - Free a line
617 * @line: line to free or NULL
618 *
619 * This frees a line that was previously allocated via term_line_free(). All its
620 * cells are released, too.
621 *
622 * If @line is NULL, this is a no-op.
623 */
624 term_line *term_line_free(term_line *line) {
625 if (!line)
626 return NULL;
627
628 term_cell_destroy_n(line->cells, line->n_cells);
629 free(line->cells);
630 free(line);
631
632 return NULL;
633 }
634
635 /**
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
642 *
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
646 * the line.
647 *
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
650 * width.
651 *
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.
655 *
656 * Returns: 0 on success, negative error code on failure.
657 */
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;
660 term_cell *t;
661
662 assert_return(line, -EINVAL);
663
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,
669 attr,
670 age);
671
672 /* allocate new cells if required */
673
674 if (width > line->n_cells) {
675 t = realloc_multiply(line->cells, sizeof(*t), width);
676 if (!t)
677 return -ENOMEM;
678
679 if (!attr && !age)
680 memzero(t + line->n_cells,
681 sizeof(*t) * (width - line->n_cells));
682 else
683 term_cell_init_n(t + line->n_cells,
684 width - line->n_cells,
685 attr,
686 age);
687
688 line->cells = t;
689 line->n_cells = width;
690 }
691
692 line->fill = MIN(line->fill, protect_width);
693
694 return 0;
695 }
696
697 /**
698 * term_line_set_width() - Change width of a line
699 * @line: line to modify
700 * @width: new width
701 *
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.
705 *
706 * This does not modify any cells. Use term_line_reserve() or term_line_erase()
707 * to clear any newly added cells.
708 *
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
712 * adjusted.
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.
717 */
718 void term_line_set_width(term_line *line, unsigned int width) {
719 assert(line);
720
721 if (width > line->n_cells)
722 width = line->n_cells;
723
724 line->width = width;
725 line->fill = MIN(line->fill, width);
726 }
727
728 /**
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
736 *
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.
740 *
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.
745 */
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;
748
749 if (from >= line->width)
750 return;
751 if (from + num < from || from + num > line->width)
752 num = line->width - from;
753 if (!num)
754 return;
755
756 move = line->width - from - num;
757 rem = MIN(num, move);
758
759 if (rem > 0) {
760 /*
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.
764 *
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
768 * the whole line.
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.
773 */
774
775 /* destroy cells that are knocked off on the right */
776 term_cell_destroy_n(line->cells + line->width - rem, rem);
777
778 /* move remaining bulk of cells */
779 memmove(line->cells + from + num,
780 line->cells + from,
781 sizeof(*line->cells) * move);
782
783 /* invalidate cells */
784 for (i = 0; i < move; ++i)
785 line->cells[from + num + i].age = age;
786
787 /* initialize fresh head-cell */
788 term_cell_init(line->cells + from,
789 head_char,
790 head_cwidth,
791 attr,
792 age);
793
794 /* initialize fresh tail-cells */
795 term_cell_init_n(line->cells + from + 1,
796 num - 1,
797 attr,
798 age);
799
800 /* adjust fill-state */
801 line->fill = MIN(line->width,
802 MAX(line->fill + num,
803 from + num));
804 } else {
805 /* modify head-cell */
806 term_cell_set(line->cells + from,
807 head_char,
808 head_cwidth,
809 attr,
810 age);
811
812 /* reset tail-cells */
813 term_cell_clear_n(line->cells + from + 1,
814 num - 1,
815 attr,
816 age);
817
818 /* adjust fill-state */
819 line->fill = line->width;
820 }
821 }
822
823 /**
824 * term_line_write() - Write to a single, specific cell
825 * @line: line to write to
826 * @pos_x: x-position of cell in @line to write to
827 * @ch: character to write to the cell
828 * @cwidth: character width of @ch
829 * @attr: attributes to set on the cell or NULL
830 * @age: current age for all modifications
831 * @insert_mode: true if INSERT-MODE is enabled
832 *
833 * This writes to a specific cell in a line. The cell is addressed by its
834 * X-position @pos_x. If that cell does not exist, this is a no-op.
835 *
836 * @ch and @attr are set on this cell.
837 *
838 * If @insert_mode is true, this inserts the character instead of overwriting
839 * existing data (existing data is now moved to the right before writing).
840 *
841 * This function is the low-level handler of normal writes to a terminal.
842 */
843 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) {
844 unsigned int len;
845
846 assert(line);
847
848 if (pos_x >= line->width)
849 return;
850
851 len = MAX(1U, cwidth);
852 if (pos_x + len < pos_x || pos_x + len > line->width)
853 len = line->width - pos_x;
854 if (!len)
855 return;
856
857 if (insert_mode) {
858 /* Use line_insert() to insert the character-head and fill
859 * the remains with NULLs. */
860 line_insert(line, pos_x, len, ch, cwidth, attr, age);
861 } else {
862 /* modify head-cell */
863 term_cell_set(line->cells + pos_x, ch, cwidth, attr, age);
864
865 /* reset tail-cells */
866 term_cell_clear_n(line->cells + pos_x + 1,
867 len - 1,
868 attr,
869 age);
870
871 /* adjust fill-state */
872 line->fill = MIN(line->width,
873 MAX(line->fill,
874 pos_x + len));
875 }
876 }
877
878 /**
879 * term_line_insert() - Insert empty cells
880 * @line: line to insert empty cells into
881 * @from: x-position where to insert cells
882 * @num: number of cells to insert
883 * @attr: attributes to set on the cells or NULL
884 * @age: current age for all modifications
885 *
886 * This inserts @num empty cells at position @from in line @line. All existing
887 * cells to the right are shifted to make room for the new cells. Cells that get
888 * pushed beyond the right hand border are discarded.
889 */
890 void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) {
891 /* use line_insert() to insert @num empty cells */
892 return line_insert(line, from, num, TERM_CHAR_NULL, 0, attr, age);
893 }
894
895 /**
896 * term_line_delete() - Delete cells from line
897 * @line: line to delete cells from
898 * @from: position to delete cells at
899 * @num: number of cells to delete
900 * @attr: attributes to set on any new cells
901 * @age: current age for all modifications
902 *
903 * Delete cells from a line. All cells to the right of the deleted cells are
904 * shifted to the left to fill the empty space. New cells appearing on the right
905 * hand border are cleared and initialized with @attr.
906 */
907 void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) {
908 unsigned int rem, move, i;
909
910 assert(line);
911
912 if (from >= line->width)
913 return;
914 if (from + num < from || from + num > line->width)
915 num = line->width - from;
916 if (!num)
917 return;
918
919 /* destroy and move as many upfront as possible */
920 move = line->width - from - num;
921 rem = MIN(num, move);
922 if (rem > 0) {
923 /* destroy to be removed cells */
924 term_cell_destroy_n(line->cells + from, rem);
925
926 /* move tail upfront */
927 memmove(line->cells + from,
928 line->cells + from + num,
929 sizeof(*line->cells) * move);
930
931 /* invalidate copied cells */
932 for (i = 0; i < move; ++i)
933 line->cells[from + i].age = age;
934
935 /* initialize tail that was moved away */
936 term_cell_init_n(line->cells + line->width - rem,
937 rem,
938 attr,
939 age);
940
941 /* reset remaining cells in case the move was too small */
942 if (num > move)
943 term_cell_clear_n(line->cells + from + move,
944 num - move,
945 attr,
946 age);
947 } else {
948 /* reset cells */
949 term_cell_clear_n(line->cells + from,
950 num,
951 attr,
952 age);
953 }
954
955 /* adjust fill-state */
956 if (from + num < line->fill)
957 line->fill -= num;
958 else if (from < line->fill)
959 line->fill = from;
960 }
961
962 /**
963 * term_line_append_combchar() - Append combining char to existing cell
964 * @line: line to modify
965 * @pos_x: position of cell to append combining char to
966 * @ucs4: combining character to append
967 * @age: current age for all modifications
968 *
969 * Unicode allows trailing combining characters, which belong to the
970 * char in front of them. The caller is responsible of detecting
971 * combining characters and calling term_line_append_combchar() instead of
972 * term_line_write(). This simply appends the char to the correct cell then.
973 * If the cell is not in the visible area, this call is skipped.
974 *
975 * Note that control-sequences are not 100% compatible with combining
976 * characters as they require delayed parsing. However, we must handle
977 * control-sequences immediately. Therefore, there might be trailing
978 * combining chars that should be discarded by the parser.
979 * However, to prevent programming errors, we're also being pedantic
980 * here and discard weirdly placed combining chars. This prevents
981 * situations were invalid content is parsed into the terminal and you
982 * might end up with cells containing only combining chars.
983 *
984 * Long story short: To get combining-characters working with old-fashioned
985 * terminal-emulation, we parse them exclusively for direct cell-writes. Other
986 * combining-characters are usually simply discarded and ignored.
987 */
988 void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age) {
989 assert(line);
990
991 if (pos_x >= line->width)
992 return;
993
994 /* Unused cell? Skip appending any combining chars then. */
995 if (term_char_is_null(line->cells[pos_x].ch))
996 return;
997
998 term_cell_append(line->cells + pos_x, ucs4, age);
999 }
1000
1001 /**
1002 * term_line_erase() - Erase parts of a line
1003 * @line: line to modify
1004 * @from: position to start the erase
1005 * @num: number of cells to erase
1006 * @attr: attributes to initialize erased cells with
1007 * @age: current age for all modifications
1008 * @keep_protected: true if protected cells should be kept
1009 *
1010 * This is the standard erase operation. It clears all cells in the targeted
1011 * area and re-initializes them. Cells to the right are not shifted left, you
1012 * must use DELETE to achieve that. Cells outside the visible area are skipped.
1013 *
1014 * If @keep_protected is true, protected cells will not be erased.
1015 */
1016 void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected) {
1017 term_cell *cell;
1018 unsigned int i, last_protected;
1019
1020 assert(line);
1021
1022 if (from >= line->width)
1023 return;
1024 if (from + num < from || from + num > line->width)
1025 num = line->width - from;
1026 if (!num)
1027 return;
1028
1029 last_protected = 0;
1030 for (i = 0; i < num; ++i) {
1031 cell = line->cells + from + i;
1032 if (keep_protected && cell->attr.protect) {
1033 /* only count protected-cells inside the fill-region */
1034 if (from + i < line->fill)
1035 last_protected = from + i;
1036
1037 continue;
1038 }
1039
1040 term_cell_set(cell, TERM_CHAR_NULL, 0, attr, age);
1041 }
1042
1043 /* Adjust fill-state. This is a bit tricks, we can only adjust it in
1044 * case the erase-region starts inside the fill-region and ends at the
1045 * tail or beyond the fill-region. Otherwise, the current fill-state
1046 * stays as it was.
1047 * Furthermore, we must account for protected cells. The loop above
1048 * ensures that protected-cells are only accounted for if they're
1049 * inside the fill-region. */
1050 if (from < line->fill && from + num >= line->fill)
1051 line->fill = MAX(from, last_protected);
1052 }
1053
1054 /**
1055 * term_line_reset() - Reset a line
1056 * @line: line to reset
1057 * @attr: attributes to initialize all cells with
1058 * @age: current age for all modifications
1059 *
1060 * This resets all visible cells of a line and sets their attributes and ages
1061 * to @attr and @age. This is equivalent to erasing a whole line via
1062 * term_line_erase().
1063 */
1064 void term_line_reset(term_line *line, const term_attr *attr, term_age_t age) {
1065 assert(line);
1066
1067 return term_line_erase(line, 0, line->width, attr, age, 0);
1068 }
1069
1070 /**
1071 * term_line_link() - Link line in front of a list
1072 * @line: line to link
1073 * @first: member pointing to first entry
1074 * @last: member pointing to last entry
1075 *
1076 * This links a line into a list of lines. The line is inserted at the front and
1077 * must not be linked, yet. See the TERM_LINE_LINK() macro for an easier usage of
1078 * this.
1079 */
1080 void term_line_link(term_line *line, term_line **first, term_line **last) {
1081 assert(line);
1082 assert(first);
1083 assert(last);
1084 assert(!line->lines_prev);
1085 assert(!line->lines_next);
1086
1087 line->lines_prev = NULL;
1088 line->lines_next = *first;
1089 if (*first)
1090 (*first)->lines_prev = line;
1091 else
1092 *last = line;
1093 *first = line;
1094 }
1095
1096 /**
1097 * term_line_link_tail() - Link line at tail of a list
1098 * @line: line to link
1099 * @first: member pointing to first entry
1100 * @last: member pointing to last entry
1101 *
1102 * Same as term_line_link() but links the line at the tail.
1103 */
1104 void term_line_link_tail(term_line *line, term_line **first, term_line **last) {
1105 assert(line);
1106 assert(first);
1107 assert(last);
1108 assert(!line->lines_prev);
1109 assert(!line->lines_next);
1110
1111 line->lines_next = NULL;
1112 line->lines_prev = *last;
1113 if (*last)
1114 (*last)->lines_next = line;
1115 else
1116 *first = line;
1117 *last = line;
1118 }
1119
1120 /**
1121 * term_line_unlink() - Unlink line from a list
1122 * @line: line to unlink
1123 * @first: member pointing to first entry
1124 * @last: member pointing to last entry
1125 *
1126 * This unlinks a previously linked line. See TERM_LINE_UNLINK() for an easier to
1127 * use macro.
1128 */
1129 void term_line_unlink(term_line *line, term_line **first, term_line **last) {
1130 assert(line);
1131 assert(first);
1132 assert(last);
1133
1134 if (line->lines_prev)
1135 line->lines_prev->lines_next = line->lines_next;
1136 else
1137 *first = line->lines_next;
1138 if (line->lines_next)
1139 line->lines_next->lines_prev = line->lines_prev;
1140 else
1141 *last = line->lines_prev;
1142
1143 line->lines_prev = NULL;
1144 line->lines_next = NULL;
1145 }
1146
1147 /**
1148 * term_page_new() - Allocate new page
1149 * @out: storage for pointer to new page
1150 *
1151 * Allocate a new page. The initial dimensions are 0/0.
1152 *
1153 * Returns: 0 on success, negative error code on failure.
1154 */
1155 int term_page_new(term_page **out) {
1156 _term_page_free_ term_page *page = NULL;
1157
1158 assert_return(out, -EINVAL);
1159
1160 page = new0(term_page, 1);
1161 if (!page)
1162 return -ENOMEM;
1163
1164 *out = page;
1165 page = NULL;
1166 return 0;
1167 }
1168
1169 /**
1170 * term_page_free() - Free page
1171 * @page: page to free or NULL
1172 *
1173 * Free a previously allocated page and all associated data. If @page is NULL,
1174 * this is a no-op.
1175 *
1176 * Returns: NULL
1177 */
1178 term_page *term_page_free(term_page *page) {
1179 unsigned int i;
1180
1181 if (!page)
1182 return NULL;
1183
1184 for (i = 0; i < page->n_lines; ++i)
1185 term_line_free(page->lines[i]);
1186
1187 free(page->line_cache);
1188 free(page->lines);
1189 free(page);
1190
1191 return NULL;
1192 }
1193
1194 /**
1195 * term_page_get_cell() - Return pointer to requested cell
1196 * @page: page to operate on
1197 * @x: x-position of cell
1198 * @y: y-position of cell
1199 *
1200 * This returns a pointer to the cell at position @x/@y. You're free to modify
1201 * this cell as much as you like. However, once you call any other function on
1202 * the page, you must drop the pointer to the cell.
1203 *
1204 * Returns: Pointer to the cell or NULL if out of the visible area.
1205 */
1206 term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y) {
1207 assert_return(page, NULL);
1208
1209 if (x >= page->width)
1210 return NULL;
1211 if (y >= page->height)
1212 return NULL;
1213
1214 return &page->lines[y]->cells[x];
1215 }
1216
1217 /**
1218 * page_scroll_up() - Scroll up
1219 * @page: page to operate on
1220 * @new_width: width to use for any new line moved into the visible area
1221 * @num: number of lines to scroll up
1222 * @attr: attributes to set on new lines
1223 * @age: age to use for all modifications
1224 * @history: history to use for old lines or NULL
1225 *
1226 * This scrolls the scroll-region by @num lines. New lines are cleared and reset
1227 * with the given attributes. Old lines are moved into the history if non-NULL.
1228 * If a new line is allocated, moved from the history buffer or moved from
1229 * outside the visible region into the visible region, this call makes sure it
1230 * has at least @width cells allocated. If a possible memory-allocation fails,
1231 * the previous line is reused. This has the side effect, that it will not be
1232 * linked into the history buffer.
1233 *
1234 * If the scroll-region is empty, this is a no-op.
1235 */
1236 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) {
1237 term_line *line, **cache;
1238 unsigned int i;
1239 int r;
1240
1241 assert(page);
1242
1243 if (num > page->scroll_num)
1244 num = page->scroll_num;
1245 if (num < 1)
1246 return;
1247
1248 /* Better safe than sorry: avoid under-allocating lines, even when
1249 * resizing. */
1250 new_width = MAX(new_width, page->width);
1251
1252 cache = page->line_cache;
1253
1254 /* Try moving lines into history and allocate new lines for each moved
1255 * line. In case allocation fails, or if we have no history, reuse the
1256 * line.
1257 * We keep the lines in the line-cache so we can safely move the
1258 * remaining lines around. */
1259 for (i = 0; i < num; ++i) {
1260 line = page->lines[page->scroll_idx + i];
1261
1262 r = -EAGAIN;
1263 if (history) {
1264 r = term_line_new(&cache[i]);
1265 if (r >= 0) {
1266 r = term_line_reserve(cache[i],
1267 new_width,
1268 attr,
1269 age,
1270 0);
1271 if (r < 0)
1272 term_line_free(cache[i]);
1273 else
1274 term_line_set_width(cache[i], page->width);
1275 }
1276 }
1277
1278 if (r >= 0) {
1279 term_history_push(history, line);
1280 } else {
1281 cache[i] = line;
1282 term_line_reset(line, attr, age);
1283 }
1284 }
1285
1286 if (num < page->scroll_num) {
1287 memmove(page->lines + page->scroll_idx,
1288 page->lines + page->scroll_idx + num,
1289 sizeof(*page->lines) * (page->scroll_num - num));
1290
1291 /* update age of moved lines */
1292 for (i = 0; i < page->scroll_num - num; ++i)
1293 page->lines[page->scroll_idx + i]->age = age;
1294 }
1295
1296 /* copy remaining lines from cache; age is already updated */
1297 memcpy(page->lines + page->scroll_idx + page->scroll_num - num,
1298 cache,
1299 sizeof(*cache) * num);
1300
1301 /* update fill */
1302 page->scroll_fill -= MIN(page->scroll_fill, num);
1303 }
1304
1305 /**
1306 * page_scroll_down() - Scroll down
1307 * @page: page to operate on
1308 * @new_width: width to use for any new line moved into the visible area
1309 * @num: number of lines to scroll down
1310 * @attr: attributes to set on new lines
1311 * @age: age to use for all modifications
1312 * @history: history to use for new lines or NULL
1313 *
1314 * This scrolls the scroll-region by @num lines. New lines are retrieved from
1315 * the history or cleared if the history is empty or NULL.
1316 *
1317 * Usually, scroll-down implies that new lines are cleared. Therefore, you're
1318 * highly encouraged to set @history to NULL. However, if you resize a terminal,
1319 * you might want to include history-lines in the new area. In that case, you
1320 * should set @history to non-NULL.
1321 *
1322 * If a new line is allocated, moved from the history buffer or moved from
1323 * outside the visible region into the visible region, this call makes sure it
1324 * has at least @width cells allocated. If a possible memory-allocation fails,
1325 * the previous line is reused. This will have the side-effect that lines from
1326 * the history will not get visible on-screen but kept in history.
1327 *
1328 * If the scroll-region is empty, this is a no-op.
1329 */
1330 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) {
1331 term_line *line, **cache, *t;
1332 unsigned int i, last_idx;
1333
1334 assert(page);
1335
1336 if (num > page->scroll_num)
1337 num = page->scroll_num;
1338 if (num < 1)
1339 return;
1340
1341 /* Better safe than sorry: avoid under-allocating lines, even when
1342 * resizing. */
1343 new_width = MAX(new_width, page->width);
1344
1345 cache = page->line_cache;
1346 last_idx = page->scroll_idx + page->scroll_num - 1;
1347
1348 /* Try pulling out lines from history; if history is empty or if no
1349 * history is given, we reuse the to-be-removed lines. Otherwise, those
1350 * lines are released. */
1351 for (i = 0; i < num; ++i) {
1352 line = page->lines[last_idx - i];
1353
1354 t = NULL;
1355 if (history)
1356 t = term_history_pop(history, new_width, attr, age);
1357
1358 if (t) {
1359 cache[num - 1 - i] = t;
1360 term_line_free(line);
1361 } else {
1362 cache[num - 1 - i] = line;
1363 term_line_reset(line, attr, age);
1364 }
1365 }
1366
1367 if (num < page->scroll_num) {
1368 memmove(page->lines + page->scroll_idx + num,
1369 page->lines + page->scroll_idx,
1370 sizeof(*page->lines) * (page->scroll_num - num));
1371
1372 /* update age of moved lines */
1373 for (i = 0; i < page->scroll_num - num; ++i)
1374 page->lines[page->scroll_idx + num + i]->age = age;
1375 }
1376
1377 /* copy remaining lines from cache; age is already updated */
1378 memcpy(page->lines + page->scroll_idx,
1379 cache,
1380 sizeof(*cache) * num);
1381
1382 /* update fill; but only if there's already content in it */
1383 if (page->scroll_fill > 0)
1384 page->scroll_fill = MIN(page->scroll_num,
1385 page->scroll_fill + num);
1386 }
1387
1388 /**
1389 * page_reserve() - Reserve page area
1390 * @page: page to modify
1391 * @cols: required columns (width)
1392 * @rows: required rows (height)
1393 * @attr: attributes for newly allocated cells
1394 * @age: age to set on any modified cells
1395 *
1396 * This allocates the required amount of lines and cells to guarantee that the
1397 * page has at least the demanded dimensions of @cols x @rows. Note that this
1398 * never shrinks the page-memory. We keep cells allocated for performance
1399 * reasons.
1400 *
1401 * Additionally to allocating lines, this also clears any newly added cells so
1402 * you can safely change the size afterwards without clearing new cells.
1403 *
1404 * Note that you must be careful what operations you call on the page between
1405 * page_reserve() and updating page->width/height. Any newly allocated line (or
1406 * shifted line) might not meet your new width/height expectations.
1407 *
1408 * Returns: 0 on success, negative error code on failure.
1409 */
1410 int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age) {
1411 _term_line_free_ term_line *line = NULL;
1412 unsigned int i, min_lines;
1413 term_line **t;
1414 int r;
1415
1416 assert_return(page, -EINVAL);
1417
1418 /*
1419 * First make sure the first MIN(page->n_lines, rows) lines have at
1420 * least the required width of @cols. This does not modify any visible
1421 * cells in the existing @page->width x @page->height area, therefore,
1422 * we can safely bail out afterwards in case anything else fails.
1423 * Note that lines in between page->height and page->n_lines might be
1424 * shorter than page->width. Hence, we need to resize them all, but we
1425 * can skip some of them for better performance.
1426 */
1427 min_lines = MIN(page->n_lines, rows);
1428 for (i = 0; i < min_lines; ++i) {
1429 /* lines below page->height have at least page->width cells */
1430 if (cols < page->width && i < page->height)
1431 continue;
1432
1433 r = term_line_reserve(page->lines[i],
1434 cols,
1435 attr,
1436 age,
1437 (i < page->height) ? page->width : 0);
1438 if (r < 0)
1439 return r;
1440 }
1441
1442 /*
1443 * We now know the first @min_lines lines have at least width @cols and
1444 * are prepared for resizing. We now only have to allocate any
1445 * additional lines below @min_lines in case @rows is greater than
1446 * page->n_lines.
1447 */
1448 if (rows > page->n_lines) {
1449 t = realloc_multiply(page->lines, sizeof(*t), rows);
1450 if (!t)
1451 return -ENOMEM;
1452 page->lines = t;
1453
1454 t = realloc_multiply(page->line_cache, sizeof(*t), rows);
1455 if (!t)
1456 return -ENOMEM;
1457 page->line_cache = t;
1458
1459 while (page->n_lines < rows) {
1460 r = term_line_new(&line);
1461 if (r < 0)
1462 return r;
1463
1464 r = term_line_reserve(line, cols, attr, age, 0);
1465 if (r < 0)
1466 return r;
1467
1468 page->lines[page->n_lines++] = line;
1469 line = NULL;
1470 }
1471 }
1472
1473 return 0;
1474 }
1475
1476 /**
1477 * term_page_resize() - Resize page
1478 * @page: page to modify
1479 * @cols: number of columns (width)
1480 * @rows: number of rows (height)
1481 * @attr: attributes for newly allocated cells
1482 * @age: age to set on any modified cells
1483 * @history: history buffer to use for new/old lines or NULL
1484 *
1485 * This changes the visible dimensions of a page. You must have called
1486 * term_page_reserve() beforehand, otherwise, this will fail.
1487 *
1488 * Returns: 0 on success, negative error code on failure.
1489 */
1490 void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history) {
1491 unsigned int i, num, empty, max, old_height;
1492 term_line *line;
1493
1494 assert(page);
1495 assert(page->n_lines >= rows);
1496
1497 old_height = page->height;
1498
1499 if (rows < old_height) {
1500 /*
1501 * If we decrease the terminal-height, we emulate a scroll-up.
1502 * This way, existing data from the scroll-area is moved into
1503 * the history, making space at the bottom to reduce the screen
1504 * height. In case the scroll-fill indicates empty lines, we
1505 * reduce the amount of scrolled lines.
1506 * Once scrolled, we have to move the lower margin from below
1507 * the scroll area up so it is preserved.
1508 */
1509
1510 /* move lines to history if scroll region is filled */
1511 num = old_height - rows;
1512 empty = page->scroll_num - page->scroll_fill;
1513 if (num > empty)
1514 page_scroll_up(page,
1515 cols,
1516 num - empty,
1517 attr,
1518 age,
1519 history);
1520
1521 /* move lower margin up; drop its lines if not enough space */
1522 num = LESS_BY(old_height, page->scroll_idx + page->scroll_num);
1523 max = LESS_BY(rows, page->scroll_idx);
1524 num = MIN(num, max);
1525 if (num > 0) {
1526 unsigned int top, bottom;
1527
1528 top = rows - num;
1529 bottom = page->scroll_idx + page->scroll_num;
1530
1531 /* might overlap; must run topdown, not bottomup */
1532 for (i = 0; i < num; ++i) {
1533 line = page->lines[top + i];
1534 page->lines[top + i] = page->lines[bottom + i];
1535 page->lines[bottom + i] = line;
1536 }
1537 }
1538
1539 /* update vertical extents */
1540 page->height = rows;
1541 page->scroll_idx = MIN(page->scroll_idx, rows);
1542 page->scroll_num -= MIN(page->scroll_num, old_height - rows);
1543 /* fill is already up-to-date or 0 due to scroll-up */
1544 } else if (rows > old_height) {
1545 /*
1546 * If we increase the terminal-height, we emulate a scroll-down
1547 * and fetch new lines from the history.
1548 * New lines are always accounted to the scroll-region. Thus we
1549 * have to preserve the lower margin first, by moving it down.
1550 */
1551
1552 /* move lower margin down */
1553 num = LESS_BY(old_height, page->scroll_idx + page->scroll_num);
1554 if (num > 0) {
1555 unsigned int top, bottom;
1556
1557 top = page->scroll_idx + page->scroll_num;
1558 bottom = top + (rows - old_height);
1559
1560 /* might overlap; must run bottomup, not topdown */
1561 for (i = num; i-- > 0; ) {
1562 line = page->lines[top + i];
1563 page->lines[top + i] = page->lines[bottom + i];
1564 page->lines[bottom + i] = line;
1565 }
1566 }
1567
1568 /* update vertical extents */
1569 page->height = rows;
1570 page->scroll_num = MIN(LESS_BY(rows, page->scroll_idx),
1571 page->scroll_num + (rows - old_height));
1572
1573 /* check how many lines can be received from history */
1574 if (history)
1575 num = term_history_peek(history,
1576 rows - old_height,
1577 cols,
1578 attr,
1579 age);
1580 else
1581 num = 0;
1582
1583 /* retrieve new lines from history if available */
1584 if (num > 0)
1585 page_scroll_down(page,
1586 cols,
1587 num,
1588 attr,
1589 age,
1590 history);
1591 }
1592
1593 /* set horizontal extents */
1594 page->width = cols;
1595 for (i = 0; i < page->height; ++i)
1596 term_line_set_width(page->lines[i], cols);
1597 }
1598
1599 /**
1600 * term_page_write() - Write to a single cell
1601 * @page: page to operate on
1602 * @pos_x: x-position of cell to write to
1603 * @pos_y: y-position of cell to write to
1604 * @ch: character to write
1605 * @cwidth: character-width of @ch
1606 * @attr: attributes to set on the cell or NULL
1607 * @age: age to use for all modifications
1608 * @insert_mode: true if INSERT-MODE is enabled
1609 *
1610 * This writes a character to a specific cell. If the cell is beyond bounds,
1611 * this is a no-op. @attr and @age are used to update the cell. @flags can be
1612 * used to alter the behavior of this function.
1613 *
1614 * This is a wrapper around term_line_write().
1615 *
1616 * This call does not wrap around lines. That is, this only operates on a single
1617 * line.
1618 */
1619 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) {
1620 assert(page);
1621
1622 if (pos_y >= page->height)
1623 return;
1624
1625 term_line_write(page->lines[pos_y], pos_x, ch, cwidth, attr, age, insert_mode);
1626 }
1627
1628 /**
1629 * term_page_insert_cells() - Insert cells into a line
1630 * @page: page to operate on
1631 * @from_x: x-position where to insert new cells
1632 * @from_y: y-position where to insert new cells
1633 * @num: number of cells to insert
1634 * @attr: attributes to set on new cells or NULL
1635 * @age: age to use for all modifications
1636 *
1637 * This inserts new cells into a given line. This is a wrapper around
1638 * term_line_insert().
1639 *
1640 * This call does not wrap around lines. That is, this only operates on a single
1641 * line.
1642 */
1643 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) {
1644 assert(page);
1645
1646 if (from_y >= page->height)
1647 return;
1648
1649 term_line_insert(page->lines[from_y], from_x, num, attr, age);
1650 }
1651
1652 /**
1653 * term_page_delete_cells() - Delete cells from a line
1654 * @page: page to operate on
1655 * @from_x: x-position where to delete cells
1656 * @from_y: y-position where to delete cells
1657 * @num: number of cells to delete
1658 * @attr: attributes to set on new cells or NULL
1659 * @age: age to use for all modifications
1660 *
1661 * This deletes cells from a given line. This is a wrapper around
1662 * term_line_delete().
1663 *
1664 * This call does not wrap around lines. That is, this only operates on a single
1665 * line.
1666 */
1667 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) {
1668 assert(page);
1669
1670 if (from_y >= page->height)
1671 return;
1672
1673 term_line_delete(page->lines[from_y], from_x, num, attr, age);
1674 }
1675
1676 /**
1677 * term_page_append_combchar() - Append combining-character to a cell
1678 * @page: page to operate on
1679 * @pos_x: x-position of target cell
1680 * @pos_y: y-position of target cell
1681 * @ucs4: combining character to append
1682 * @age: age to use for all modifications
1683 *
1684 * This appends a combining-character to a specific cell. This is a wrapper
1685 * around term_line_append_combchar().
1686 */
1687 void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age) {
1688 assert(page);
1689
1690 if (pos_y >= page->height)
1691 return;
1692
1693 term_line_append_combchar(page->lines[pos_y], pos_x, ucs4, age);
1694 }
1695
1696 /**
1697 * term_page_erase() - Erase parts of a page
1698 * @page: page to operate on
1699 * @from_x: x-position where to start erasure (inclusive)
1700 * @from_y: y-position where to start erasure (inclusive)
1701 * @to_x: x-position where to stop erasure (inclusive)
1702 * @to_y: y-position where to stop erasure (inclusive)
1703 * @attr: attributes to set on cells
1704 * @age: age to use for all modifications
1705 * @keep_protected: true if protected cells should be kept
1706 *
1707 * This erases all cells starting at @from_x/@from_y up to @to_x/@to_y. Note
1708 * that this wraps around line-boundaries so lines between @from_y and @to_y
1709 * are cleared entirely.
1710 *
1711 * Lines outside the visible area are left untouched.
1712 */
1713 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) {
1714 unsigned int i, from, to;
1715
1716 assert(page);
1717
1718 for (i = from_y; i <= to_y && i < page->height; ++i) {
1719 from = 0;
1720 to = page->width;
1721
1722 if (i == from_y)
1723 from = from_x;
1724 if (i == to_y)
1725 to = to_x;
1726
1727 term_line_erase(page->lines[i],
1728 from,
1729 LESS_BY(to, from),
1730 attr,
1731 age,
1732 keep_protected);
1733 }
1734 }
1735
1736 /**
1737 * term_page_reset() - Reset page
1738 * @page: page to modify
1739 * @attr: attributes to set on cells
1740 * @age: age to use for all modifications
1741 *
1742 * This erases the whole visible page. See term_page_erase().
1743 */
1744 void term_page_reset(term_page *page, const term_attr *attr, term_age_t age) {
1745 assert(page);
1746
1747 return term_page_erase(page,
1748 0, 0,
1749 page->width - 1, page->height - 1,
1750 attr,
1751 age,
1752 0);
1753 }
1754
1755 /**
1756 * term_page_set_scroll_region() - Set scroll region
1757 * @page: page to operate on
1758 * @idx: start-index of scroll region
1759 * @num: number of lines in scroll region
1760 *
1761 * This sets the scroll region of a page. Whenever an operation needs to scroll
1762 * lines, it scrolls them inside of that region. Lines outside the region are
1763 * left untouched. In case a scroll-operation is targeted outside of this
1764 * region, it will implicitly get a scroll-region of only one line (i.e., no
1765 * scroll region at all).
1766 *
1767 * Note that the scroll-region is clipped to the current page-extents. Growing
1768 * or shrinking the page always accounts new/old lines to the scroll region and
1769 * moves top/bottom margins accordingly so they're preserved.
1770 */
1771 void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num) {
1772 assert(page);
1773
1774 if (page->height < 1) {
1775 page->scroll_idx = 0;
1776 page->scroll_num = 0;
1777 } else {
1778 page->scroll_idx = MIN(idx, page->height - 1);
1779 page->scroll_num = MIN(num, page->height - page->scroll_idx);
1780 }
1781 }
1782
1783 /**
1784 * term_page_scroll_up() - Scroll up
1785 * @page: page to operate on
1786 * @num: number of lines to scroll up
1787 * @attr: attributes to set on new lines
1788 * @age: age to use for all modifications
1789 * @history: history to use for old lines or NULL
1790 *
1791 * This scrolls the scroll-region by @num lines. New lines are cleared and reset
1792 * with the given attributes. Old lines are moved into the history if non-NULL.
1793 *
1794 * If the scroll-region is empty, this is a no-op.
1795 */
1796 void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1797 page_scroll_up(page, page->width, num, attr, age, history);
1798 }
1799
1800 /**
1801 * term_page_scroll_down() - Scroll down
1802 * @page: page to operate on
1803 * @num: number of lines to scroll down
1804 * @attr: attributes to set on new lines
1805 * @age: age to use for all modifications
1806 * @history: history to use for new lines or NULL
1807 *
1808 * This scrolls the scroll-region by @num lines. New lines are retrieved from
1809 * the history or cleared if the history is empty or NULL.
1810 *
1811 * Usually, scroll-down implies that new lines are cleared. Therefore, you're
1812 * highly encouraged to set @history to NULL. However, if you resize a terminal,
1813 * you might want to include history-lines in the new area. In that case, you
1814 * should set @history to non-NULL.
1815 *
1816 * If the scroll-region is empty, this is a no-op.
1817 */
1818 void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
1819 page_scroll_down(page, page->width, num, attr, age, history);
1820 }
1821
1822 /**
1823 * term_page_insert_lines() - Insert new lines
1824 * @page: page to operate on
1825 * @pos_y: y-position where to insert new lines
1826 * @num: number of lines to insert
1827 * @attr: attributes to set on new lines
1828 * @age: age to use for all modifications
1829 *
1830 * This inserts @num new lines at position @pos_y. If @pos_y is beyond
1831 * boundaries or @num is 0, this is a no-op.
1832 * All lines below @pos_y are moved down to make space for the new lines. Lines
1833 * on the bottom are dropped. Note that this only moves lines above or inside
1834 * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of
1835 * one line is implied (which means the line is simply cleared).
1836 */
1837 void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) {
1838 unsigned int scroll_idx, scroll_num;
1839
1840 assert(page);
1841
1842 if (pos_y >= page->height)
1843 return;
1844 if (num >= page->height)
1845 num = page->height;
1846
1847 /* remember scroll-region */
1848 scroll_idx = page->scroll_idx;
1849 scroll_num = page->scroll_num;
1850
1851 /* set scroll-region temporarily so we can reuse scroll_down() */
1852 {
1853 page->scroll_idx = pos_y;
1854 if (pos_y >= scroll_idx + scroll_num)
1855 page->scroll_num = 1;
1856 else if (pos_y >= scroll_idx)
1857 page->scroll_num -= pos_y - scroll_idx;
1858 else
1859 page->scroll_num += scroll_idx - pos_y;
1860
1861 term_page_scroll_down(page, num, attr, age, NULL);
1862 }
1863
1864 /* reset scroll-region */
1865 page->scroll_idx = scroll_idx;
1866 page->scroll_num = scroll_num;
1867 }
1868
1869 /**
1870 * term_page_delete_lines() - Delete lines
1871 * @page: page to operate on
1872 * @pos_y: y-position where to delete lines
1873 * @num: number of lines to delete
1874 * @attr: attributes to set on new lines
1875 * @age: age to use for all modifications
1876 *
1877 * This deletes @num lines at position @pos_y. If @pos_y is beyond boundaries or
1878 * @num is 0, this is a no-op.
1879 * All lines below @pos_y are moved up into the newly made space. New lines
1880 * on the bottom are clear. Note that this only moves lines above or inside
1881 * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of
1882 * one line is implied (which means the line is simply cleared).
1883 */
1884 void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) {
1885 unsigned int scroll_idx, scroll_num;
1886
1887 assert(page);
1888
1889 if (pos_y >= page->height)
1890 return;
1891 if (num >= page->height)
1892 num = page->height;
1893
1894 /* remember scroll-region */
1895 scroll_idx = page->scroll_idx;
1896 scroll_num = page->scroll_num;
1897
1898 /* set scroll-region temporarily so we can reuse scroll_up() */
1899 {
1900 page->scroll_idx = pos_y;
1901 if (pos_y >= scroll_idx + scroll_num)
1902 page->scroll_num = 1;
1903 else if (pos_y > scroll_idx)
1904 page->scroll_num -= pos_y - scroll_idx;
1905 else
1906 page->scroll_num += scroll_idx - pos_y;
1907
1908 term_page_scroll_up(page, num, attr, age, NULL);
1909 }
1910
1911 /* reset scroll-region */
1912 page->scroll_idx = scroll_idx;
1913 page->scroll_num = scroll_num;
1914 }
1915
1916 /**
1917 * term_history_new() - Create new history object
1918 * @out: storage for pointer to new history
1919 *
1920 * Create a new history object. Histories are used to store scrollback-lines
1921 * from VTE pages. You're highly recommended to set a history-limit on
1922 * history->max_lines and trim it via term_history_trim(), otherwise history
1923 * allocations are unlimited.
1924 *
1925 * Returns: 0 on success, negative error code on failure.
1926 */
1927 int term_history_new(term_history **out) {
1928 _term_history_free_ term_history *history = NULL;
1929
1930 assert_return(out, -EINVAL);
1931
1932 history = new0(term_history, 1);
1933 if (!history)
1934 return -ENOMEM;
1935
1936 history->max_lines = 4096;
1937
1938 *out = history;
1939 history = NULL;
1940 return 0;
1941 }
1942
1943 /**
1944 * term_history_free() - Free history
1945 * @history: history to free
1946 *
1947 * Clear and free history. You must not access the object afterwards.
1948 *
1949 * Returns: NULL
1950 */
1951 term_history *term_history_free(term_history *history) {
1952 if (!history)
1953 return NULL;
1954
1955 term_history_clear(history);
1956 free(history);
1957 return NULL;
1958 }
1959
1960 /**
1961 * term_history_clear() - Clear history
1962 * @history: history to clear
1963 *
1964 * Remove all linked lines from a history and reset it to its initial state.
1965 */
1966 void term_history_clear(term_history *history) {
1967 return term_history_trim(history, 0);
1968 }
1969
1970 /**
1971 * term_history_trim() - Trim history
1972 * @history: history to trim
1973 * @max: maximum number of lines to be left in history
1974 *
1975 * This removes lines from the history until it is smaller than @max. Lines are
1976 * removed from the top.
1977 */
1978 void term_history_trim(term_history *history, unsigned int max) {
1979 term_line *line;
1980
1981 if (!history)
1982 return;
1983
1984 while (history->n_lines > max && (line = history->lines_first)) {
1985 TERM_LINE_UNLINK(line, history);
1986 term_line_free(line);
1987 --history->n_lines;
1988 }
1989 }
1990
1991 /**
1992 * term_history_push() - Push line into history
1993 * @history: history to work on
1994 * @line: line to push into history
1995 *
1996 * This pushes a line into the given history. It is linked at the tail. In case
1997 * the history is limited, the top-most line might be freed.
1998 */
1999 void term_history_push(term_history *history, term_line *line) {
2000 assert(history);
2001 assert(line);
2002
2003 TERM_LINE_LINK_TAIL(line, history);
2004 if (history->max_lines > 0 && history->n_lines >= history->max_lines) {
2005 line = history->lines_first;
2006 TERM_LINE_UNLINK(line, history);
2007 term_line_free(line);
2008 } else {
2009 ++history->n_lines;
2010 }
2011 }
2012
2013 /**
2014 * term_history_pop() - Retrieve last line from history
2015 * @history: history to work on
2016 * @new_width: width to reserve and set on the line
2017 * @attr: attributes to use for cell reservation
2018 * @age: age to use for cell reservation
2019 *
2020 * This unlinks the last linked line of the history and returns it. This also
2021 * makes sure the line has the given width pre-allocated (see
2022 * term_line_reserve()). If the pre-allocation fails, this returns NULL, so it
2023 * is treated like there's no line in history left. This simplifies
2024 * history-handling on the caller's side in case of allocation errors. No need
2025 * to throw lines away just because the reservation failed. We can keep them in
2026 * history safely, and make them available as scrollback.
2027 *
2028 * Returns: Line from history or NULL
2029 */
2030 term_line *term_history_pop(term_history *history, unsigned int new_width, const term_attr *attr, term_age_t age) {
2031 term_line *line;
2032 int r;
2033
2034 assert_return(history, NULL);
2035
2036 line = history->lines_last;
2037 if (!line)
2038 return NULL;
2039
2040 r = term_line_reserve(line, new_width, attr, age, line->width);
2041 if (r < 0)
2042 return NULL;
2043
2044 term_line_set_width(line, new_width);
2045 TERM_LINE_UNLINK(line, history);
2046 --history->n_lines;
2047
2048 return line;
2049 }
2050
2051 /**
2052 * term_history_peek() - Return number of available history-lines
2053 * @history: history to work on
2054 * @max: maximum number of lines to look at
2055 * @reserve_width: width to reserve on the line
2056 * @attr: attributes to use for cell reservation
2057 * @age: age to use for cell reservation
2058 *
2059 * This returns the number of available lines in the history given as @history.
2060 * It returns at most @max. For each line that is looked at, the line is
2061 * verified to have at least @reserve_width cells. Valid cells are preserved,
2062 * new cells are initialized with @attr and @age. In case an allocation fails,
2063 * we bail out and return the number of lines that are valid so far.
2064 *
2065 * Usually, this function should be used before running a loop on
2066 * term_history_pop(). This function guarantees that term_history_pop() (with
2067 * the same arguments) will succeed at least the returned number of times.
2068 *
2069 * Returns: Number of valid lines that can be received via term_history_pop().
2070 */
2071 unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age) {
2072 unsigned int num;
2073 term_line *line;
2074 int r;
2075
2076 assert(history);
2077
2078 num = 0;
2079 line = history->lines_last;
2080
2081 while (num < max && line) {
2082 r = term_line_reserve(line, reserve_width, attr, age, line->width);
2083 if (r < 0)
2084 break;
2085
2086 ++num;
2087 line = line->lines_prev;
2088 }
2089
2090 return num;
2091 }