chiark / gitweb /
d7d2f98b357a7c39738b25f4234b175351af8a67
[elogind.git] / src / libsystemd-terminal / term-internal.h
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 #pragma once
23
24 #include <stdbool.h>
25 #include <stdint.h>
26 #include <stdlib.h>
27 #include "util.h"
28
29 typedef struct term_char term_char_t;
30 typedef struct term_charbuf term_charbuf_t;
31
32 typedef struct term_color term_color;
33 typedef struct term_attr term_attr;
34 typedef struct term_cell term_cell;
35 typedef struct term_line term_line;
36
37 typedef struct term_page term_page;
38 typedef struct term_history term_history;
39
40 /*
41  * Miscellaneous
42  * Sundry things and external helpers.
43  */
44
45 int mk_wcwidth(wchar_t ucs4);
46 int mk_wcwidth_cjk(wchar_t ucs4);
47 int mk_wcswidth(const wchar_t *str, size_t len);
48 int mk_wcswidth_cjk(const wchar_t *str, size_t len);
49
50 /*
51  * Ageing
52  * Redrawing terminals is quite expensive. Therefore, we avoid redrawing on
53  * each single modification and mark modified cells instead. This way, we know
54  * which cells to redraw on the next frame. However, a single DIRTY flag is not
55  * enough for double/triple buffered screens, hence, we use an AGE field for
56  * each cell. If the cell is modified, we simply increase the age by one. Each
57  * framebuffer can then remember its last rendered age and request an update of
58  * all newer cells.
59  * TERM_AGE_NULL is special. If used as cell age, the cell must always be
60  * redrawn (forced update). If used as framebuffer age, all cells are drawn.
61  * This way, we can allow integer wrap-arounds.
62  */
63
64 typedef uint64_t term_age_t;
65
66 #define TERM_AGE_NULL 0
67
68 /*
69  * Characters
70  * Each cell in a terminal page contains only a single character. This is
71  * usually a single UCS-4 value. However, Unicode allows combining-characters,
72  * therefore, the number of UCS-4 characters per cell must be unlimited. The
73  * term_char_t object wraps the internal combining char API so it can be
74  * treated as a single object.
75  */
76
77 struct term_char {
78         /* never access this value directly */
79         uint64_t _value;
80 };
81
82 struct term_charbuf {
83         /* 3 bytes + zero-terminator */
84         uint32_t buf[4];
85 };
86
87 #define TERM_CHAR_INIT(_val) ((term_char_t){ ._value = (_val) })
88 #define TERM_CHAR_NULL TERM_CHAR_INIT(0)
89
90 term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4);
91 term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4);
92 term_char_t term_char_dup(term_char_t ch);
93 term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4);
94
95 const uint32_t *term_char_resolve(term_char_t ch, size_t *s, term_charbuf_t *b);
96 unsigned int term_char_lookup_width(term_char_t ch);
97
98 /* true if @ch is TERM_CHAR_NULL, otherwise false */
99 static inline bool term_char_is_null(term_char_t ch) {
100         return ch._value == 0;
101 }
102
103 /* true if @ch is dynamically allocated and needs to be freed */
104 static inline bool term_char_is_allocated(term_char_t ch) {
105         return !term_char_is_null(ch) && !(ch._value & 0x1);
106 }
107
108 /* true if (a == b), otherwise false; this is (a == b), NOT (*a == *b) */
109 static inline bool term_char_same(term_char_t a, term_char_t b) {
110         return a._value == b._value;
111 }
112
113 /* true if (*a == *b), otherwise false; this is implied by (a == b) */
114 static inline bool term_char_equal(term_char_t a, term_char_t b) {
115         const uint32_t *sa, *sb;
116         term_charbuf_t ca, cb;
117         size_t na, nb;
118
119         sa = term_char_resolve(a, &na, &ca);
120         sb = term_char_resolve(b, &nb, &cb);
121         return na == nb && !memcmp(sa, sb, sizeof(*sa) * na);
122 }
123
124 /* free @ch in case it is dynamically allocated */
125 static inline term_char_t term_char_free(term_char_t ch) {
126         if (term_char_is_allocated(ch))
127                 term_char_set(ch, 0);
128
129         return TERM_CHAR_NULL;
130 }
131
132 /* gcc _cleanup_ helpers */
133 #define _term_char_free_ _cleanup_(term_char_freep)
134 static inline void term_char_freep(term_char_t *p) {
135         term_char_free(*p);
136 }
137
138 /*
139  * Attributes
140  * Each cell in a terminal page can have its own set of attributes. These alter
141  * the behavior of the renderer for this single cell. We use term_attr to
142  * specify attributes.
143  * The only non-obvious field is "ccode" for foreground and background colors.
144  * This field contains the terminal color-code in case no full RGB information
145  * was given by the host. It is also required for dynamic color palettes. If it
146  * is set to TERM_CCODE_RGB, the "red", "green" and "blue" fields contain the
147  * full RGB color.
148  */
149
150 enum {
151         /* special color-codes */
152         TERM_CCODE_DEFAULT,                                             /* default foreground/background color */
153         TERM_CCODE_256,                                                 /* 256color code */
154         TERM_CCODE_RGB,                                                 /* color is specified as RGB */
155
156         /* dark color-codes */
157         TERM_CCODE_BLACK,
158         TERM_CCODE_RED,
159         TERM_CCODE_GREEN,
160         TERM_CCODE_YELLOW,
161         TERM_CCODE_BLUE,
162         TERM_CCODE_MAGENTA,
163         TERM_CCODE_CYAN,
164         TERM_CCODE_WHITE,                                               /* technically: light grey */
165
166         /* light color-codes */
167         TERM_CCODE_LIGHT_BLACK          = TERM_CCODE_BLACK + 8,         /* technically: dark grey */
168         TERM_CCODE_LIGHT_RED            = TERM_CCODE_RED + 8,
169         TERM_CCODE_LIGHT_GREEN          = TERM_CCODE_GREEN + 8,
170         TERM_CCODE_LIGHT_YELLOW         = TERM_CCODE_YELLOW + 8,
171         TERM_CCODE_LIGHT_BLUE           = TERM_CCODE_BLUE + 8,
172         TERM_CCODE_LIGHT_MAGENTA        = TERM_CCODE_MAGENTA + 8,
173         TERM_CCODE_LIGHT_CYAN           = TERM_CCODE_CYAN + 8,
174         TERM_CCODE_LIGHT_WHITE          = TERM_CCODE_WHITE + 8,
175
176         TERM_CCODE_CNT,
177 };
178
179 struct term_color {
180         uint8_t ccode;
181         uint8_t c256;
182         uint8_t red;
183         uint8_t green;
184         uint8_t blue;
185 };
186
187 struct term_attr {
188         term_color fg;                          /* foreground color */
189         term_color bg;                          /* background color */
190
191         unsigned int bold : 1;                  /* bold font */
192         unsigned int italic : 1;                /* italic font */
193         unsigned int underline : 1;             /* underline text */
194         unsigned int inverse : 1;               /* inverse fg/bg */
195         unsigned int protect : 1;               /* protect from erase */
196         unsigned int blink : 1;                 /* blink text */
197         unsigned int hidden : 1;                /* hidden */
198 };
199
200 /*
201  * Cells
202  * The term_cell structure respresents a single cell in a terminal page. It
203  * contains the stored character, the age of the cell and all its attributes.
204  */
205
206 struct term_cell {
207         term_char_t ch;         /* stored char or TERM_CHAR_NULL */
208         term_age_t age;         /* cell age or TERM_AGE_NULL */
209         term_attr attr;         /* cell attributes */
210         unsigned int cwidth;    /* cached term_char_lookup_width(cell->ch) */
211 };
212
213 /*
214  * Lines
215  * Instead of storing cells in a 2D array, we store them in an array of
216  * dynamically allocated lines. This way, scrolling can be implemented very
217  * fast without moving any cells at all. Similarly, the scrollback-buffer is
218  * much simpler to implement.
219  * We use term_line to store a single line. It contains an array of cells, a
220  * fill-state which remembers the amount of blanks on the right side, a
221  * separate age just for the line which can overwrite the age for all cells,
222  * and some management data.
223  */
224
225 struct term_line {
226         term_line *lines_next;          /* linked-list for histories */
227         term_line *lines_prev;          /* linked-list for histories */
228
229         unsigned int width;             /* visible width of line */
230         unsigned int n_cells;           /* # of allocated cells */
231         term_cell *cells;               /* cell-array */
232
233         term_age_t age;                 /* line age */
234         unsigned int fill;              /* # of valid cells; starting left */
235 };
236
237 int term_line_new(term_line **out);
238 term_line *term_line_free(term_line *line);
239
240 #define _term_line_free_ _cleanup_(term_line_freep)
241 DEFINE_TRIVIAL_CLEANUP_FUNC(term_line*, term_line_free);
242
243 int term_line_reserve(term_line *line, unsigned int width, const term_attr *attr, term_age_t age, unsigned int protect_width);
244 void term_line_set_width(term_line *line, unsigned int width);
245 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);
246 void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age);
247 void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age);
248 void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age);
249 void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected);
250 void term_line_reset(term_line *line, const term_attr *attr, term_age_t age);
251
252 void term_line_link(term_line *line, term_line **first, term_line **last);
253 void term_line_link_tail(term_line *line, term_line **first, term_line **last);
254 void term_line_unlink(term_line *line, term_line **first, term_line **last);
255
256 #define TERM_LINE_LINK(_line, _head) term_line_link((_line), &(_head)->lines_first, &(_head)->lines_last)
257 #define TERM_LINE_LINK_TAIL(_line, _head) term_line_link_tail((_line), &(_head)->lines_first, &(_head)->lines_last)
258 #define TERM_LINE_UNLINK(_line, _head) term_line_unlink((_line), &(_head)->lines_first, &(_head)->lines_last)
259
260 /*
261  * Pages
262  * A page represents the 2D table containing all cells of a terminal. It stores
263  * lines as an array of pointers so scrolling becomes a simple line-shuffle
264  * operation.
265  * Scrolling is always targeted only at the scroll-region defined via scroll_idx
266  * and scroll_num. The fill-state keeps track of the number of touched lines in
267  * the scroll-region. @width and @height describe the visible region of the page
268  * and are guaranteed to be allocated at all times.
269  */
270
271 struct term_page {
272         term_age_t age;                 /* page age */
273
274         term_line **lines;              /* array of line-pointers */
275         term_line **line_cache;         /* cache for temporary operations */
276         unsigned int n_lines;           /* # of allocated lines */
277
278         unsigned int width;             /* width of visible area */
279         unsigned int height;            /* height of visible area */
280         unsigned int scroll_idx;        /* scrolling-region start index */
281         unsigned int scroll_num;        /* scrolling-region length in lines */
282         unsigned int scroll_fill;       /* # of valid scroll-lines */
283 };
284
285 int term_page_new(term_page **out);
286 term_page *term_page_free(term_page *page);
287
288 #define _term_page_free_ _cleanup_(term_page_freep)
289 DEFINE_TRIVIAL_CLEANUP_FUNC(term_page*, term_page_free);
290
291 term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y);
292
293 int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age);
294 void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history);
295 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);
296 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);
297 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);
298 void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age);
299 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);
300 void term_page_reset(term_page *page, const term_attr *attr, term_age_t age);
301
302 void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num);
303 void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history);
304 void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history);
305 void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age);
306 void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age);
307
308 /*
309  * Histories
310  * Scroll-back buffers use term_history objects to store scroll-back lines. A
311  * page is independent of the history used. All page operations that modify a
312  * history take it as separate argument. You're free to pass NULL at all times
313  * if no history should be used.
314  * Lines are stored in a linked list as no complex operations are ever done on
315  * history lines, besides pushing/poping. Note that history lines do not have a
316  * guaranteed minimum length. Any kind of line might be stored there. Missing
317  * cells should be cleared to the background color.
318  */
319
320 struct term_history {
321         term_line *lines_first;
322         term_line *lines_last;
323         unsigned int n_lines;
324         unsigned int max_lines;
325 };
326
327 int term_history_new(term_history **out);
328 term_history *term_history_free(term_history *history);
329
330 #define _term_history_free_ _cleanup_(term_history_freep)
331 DEFINE_TRIVIAL_CLEANUP_FUNC(term_history*, term_history_free);
332
333 void term_history_clear(term_history *history);
334 void term_history_trim(term_history *history, unsigned int max);
335 void term_history_push(term_history *history, term_line *line);
336 term_line *term_history_pop(term_history *history, unsigned int reserve_width, const term_attr *attr, term_age_t age);
337 unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age);