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