1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
29 typedef struct term_char term_char_t;
30 typedef struct term_charbuf term_charbuf_t;
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;
37 typedef struct term_page term_page;
38 typedef struct term_history term_history;
40 typedef struct term_utf8 term_utf8;
41 typedef struct term_seq term_seq;
42 typedef struct term_parser term_parser;
43 typedef uint32_t term_charset[96];
47 * Sundry things and external helpers.
50 int mk_wcwidth(wchar_t ucs4);
51 int mk_wcwidth_cjk(wchar_t ucs4);
52 int mk_wcswidth(const wchar_t *str, size_t len);
53 int mk_wcswidth_cjk(const wchar_t *str, size_t len);
57 * Redrawing terminals is quite expensive. Therefore, we avoid redrawing on
58 * each single modification and mark modified cells instead. This way, we know
59 * which cells to redraw on the next frame. However, a single DIRTY flag is not
60 * enough for double/triple buffered screens, hence, we use an AGE field for
61 * each cell. If the cell is modified, we simply increase the age by one. Each
62 * framebuffer can then remember its last rendered age and request an update of
64 * TERM_AGE_NULL is special. If used as cell age, the cell must always be
65 * redrawn (forced update). If used as framebuffer age, all cells are drawn.
66 * This way, we can allow integer wrap-arounds.
69 typedef uint64_t term_age_t;
71 #define TERM_AGE_NULL 0
75 * Each cell in a terminal page contains only a single character. This is
76 * usually a single UCS-4 value. However, Unicode allows combining-characters,
77 * therefore, the number of UCS-4 characters per cell must be unlimited. The
78 * term_char_t object wraps the internal combining char API so it can be
79 * treated as a single object.
83 /* never access this value directly */
88 /* 3 bytes + zero-terminator */
92 #define TERM_CHAR_INIT(_val) ((term_char_t){ ._value = (_val) })
93 #define TERM_CHAR_NULL TERM_CHAR_INIT(0)
95 term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4);
96 term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4);
97 term_char_t term_char_dup(term_char_t ch);
98 term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4);
100 const uint32_t *term_char_resolve(term_char_t ch, size_t *s, term_charbuf_t *b);
101 unsigned int term_char_lookup_width(term_char_t ch);
103 /* true if @ch is TERM_CHAR_NULL, otherwise false */
104 static inline bool term_char_is_null(term_char_t ch) {
105 return ch._value == 0;
108 /* true if @ch is dynamically allocated and needs to be freed */
109 static inline bool term_char_is_allocated(term_char_t ch) {
110 return !term_char_is_null(ch) && !(ch._value & 0x1);
113 /* true if (a == b), otherwise false; this is (a == b), NOT (*a == *b) */
114 static inline bool term_char_same(term_char_t a, term_char_t b) {
115 return a._value == b._value;
118 /* true if (*a == *b), otherwise false; this is implied by (a == b) */
119 static inline bool term_char_equal(term_char_t a, term_char_t b) {
120 const uint32_t *sa, *sb;
121 term_charbuf_t ca, cb;
124 sa = term_char_resolve(a, &na, &ca);
125 sb = term_char_resolve(b, &nb, &cb);
126 return na == nb && !memcmp(sa, sb, sizeof(*sa) * na);
129 /* free @ch in case it is dynamically allocated */
130 static inline term_char_t term_char_free(term_char_t ch) {
131 if (term_char_is_allocated(ch))
132 term_char_set(ch, 0);
134 return TERM_CHAR_NULL;
137 /* gcc _cleanup_ helpers */
138 #define _term_char_free_ _cleanup_(term_char_freep)
139 static inline void term_char_freep(term_char_t *p) {
145 * Each cell in a terminal page can have its own set of attributes. These alter
146 * the behavior of the renderer for this single cell. We use term_attr to
147 * specify attributes.
148 * The only non-obvious field is "ccode" for foreground and background colors.
149 * This field contains the terminal color-code in case no full RGB information
150 * was given by the host. It is also required for dynamic color palettes. If it
151 * is set to TERM_CCODE_RGB, the "red", "green" and "blue" fields contain the
156 /* special color-codes */
157 TERM_CCODE_DEFAULT, /* default foreground/background color */
158 TERM_CCODE_256, /* 256color code */
159 TERM_CCODE_RGB, /* color is specified as RGB */
161 /* dark color-codes */
169 TERM_CCODE_WHITE, /* technically: light grey */
171 /* light color-codes */
172 TERM_CCODE_LIGHT_BLACK = TERM_CCODE_BLACK + 8, /* technically: dark grey */
173 TERM_CCODE_LIGHT_RED = TERM_CCODE_RED + 8,
174 TERM_CCODE_LIGHT_GREEN = TERM_CCODE_GREEN + 8,
175 TERM_CCODE_LIGHT_YELLOW = TERM_CCODE_YELLOW + 8,
176 TERM_CCODE_LIGHT_BLUE = TERM_CCODE_BLUE + 8,
177 TERM_CCODE_LIGHT_MAGENTA = TERM_CCODE_MAGENTA + 8,
178 TERM_CCODE_LIGHT_CYAN = TERM_CCODE_CYAN + 8,
179 TERM_CCODE_LIGHT_WHITE = TERM_CCODE_WHITE + 8,
193 term_color fg; /* foreground color */
194 term_color bg; /* background color */
196 unsigned int bold : 1; /* bold font */
197 unsigned int italic : 1; /* italic font */
198 unsigned int underline : 1; /* underline text */
199 unsigned int inverse : 1; /* inverse fg/bg */
200 unsigned int protect : 1; /* protect from erase */
201 unsigned int blink : 1; /* blink text */
202 unsigned int hidden : 1; /* hidden */
207 * The term_cell structure respresents a single cell in a terminal page. It
208 * contains the stored character, the age of the cell and all its attributes.
212 term_char_t ch; /* stored char or TERM_CHAR_NULL */
213 term_age_t age; /* cell age or TERM_AGE_NULL */
214 term_attr attr; /* cell attributes */
215 unsigned int cwidth; /* cached term_char_lookup_width(cell->ch) */
220 * Instead of storing cells in a 2D array, we store them in an array of
221 * dynamically allocated lines. This way, scrolling can be implemented very
222 * fast without moving any cells at all. Similarly, the scrollback-buffer is
223 * much simpler to implement.
224 * We use term_line to store a single line. It contains an array of cells, a
225 * fill-state which remembers the amount of blanks on the right side, a
226 * separate age just for the line which can overwrite the age for all cells,
227 * and some management data.
231 term_line *lines_next; /* linked-list for histories */
232 term_line *lines_prev; /* linked-list for histories */
234 unsigned int width; /* visible width of line */
235 unsigned int n_cells; /* # of allocated cells */
236 term_cell *cells; /* cell-array */
238 term_age_t age; /* line age */
239 unsigned int fill; /* # of valid cells; starting left */
242 int term_line_new(term_line **out);
243 term_line *term_line_free(term_line *line);
245 #define _term_line_free_ _cleanup_(term_line_freep)
246 DEFINE_TRIVIAL_CLEANUP_FUNC(term_line*, term_line_free);
248 int term_line_reserve(term_line *line, unsigned int width, const term_attr *attr, term_age_t age, unsigned int protect_width);
249 void term_line_set_width(term_line *line, unsigned int width);
250 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);
251 void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age);
252 void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age);
253 void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age);
254 void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected);
255 void term_line_reset(term_line *line, const term_attr *attr, term_age_t age);
257 void term_line_link(term_line *line, term_line **first, term_line **last);
258 void term_line_link_tail(term_line *line, term_line **first, term_line **last);
259 void term_line_unlink(term_line *line, term_line **first, term_line **last);
261 #define TERM_LINE_LINK(_line, _head) term_line_link((_line), &(_head)->lines_first, &(_head)->lines_last)
262 #define TERM_LINE_LINK_TAIL(_line, _head) term_line_link_tail((_line), &(_head)->lines_first, &(_head)->lines_last)
263 #define TERM_LINE_UNLINK(_line, _head) term_line_unlink((_line), &(_head)->lines_first, &(_head)->lines_last)
267 * A page represents the 2D table containing all cells of a terminal. It stores
268 * lines as an array of pointers so scrolling becomes a simple line-shuffle
270 * Scrolling is always targeted only at the scroll-region defined via scroll_idx
271 * and scroll_num. The fill-state keeps track of the number of touched lines in
272 * the scroll-region. @width and @height describe the visible region of the page
273 * and are guaranteed to be allocated at all times.
277 term_age_t age; /* page age */
279 term_line **lines; /* array of line-pointers */
280 term_line **line_cache; /* cache for temporary operations */
281 unsigned int n_lines; /* # of allocated lines */
283 unsigned int width; /* width of visible area */
284 unsigned int height; /* height of visible area */
285 unsigned int scroll_idx; /* scrolling-region start index */
286 unsigned int scroll_num; /* scrolling-region length in lines */
287 unsigned int scroll_fill; /* # of valid scroll-lines */
290 int term_page_new(term_page **out);
291 term_page *term_page_free(term_page *page);
293 #define _term_page_free_ _cleanup_(term_page_freep)
294 DEFINE_TRIVIAL_CLEANUP_FUNC(term_page*, term_page_free);
296 term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y);
298 int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age);
299 void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history);
300 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);
301 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);
302 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);
303 void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age);
304 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);
305 void term_page_reset(term_page *page, const term_attr *attr, term_age_t age);
307 void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num);
308 void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history);
309 void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history);
310 void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age);
311 void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age);
315 * Scroll-back buffers use term_history objects to store scroll-back lines. A
316 * page is independent of the history used. All page operations that modify a
317 * history take it as separate argument. You're free to pass NULL at all times
318 * if no history should be used.
319 * Lines are stored in a linked list as no complex operations are ever done on
320 * history lines, besides pushing/poping. Note that history lines do not have a
321 * guaranteed minimum length. Any kind of line might be stored there. Missing
322 * cells should be cleared to the background color.
325 struct term_history {
326 term_line *lines_first;
327 term_line *lines_last;
328 unsigned int n_lines;
329 unsigned int max_lines;
332 int term_history_new(term_history **out);
333 term_history *term_history_free(term_history *history);
335 #define _term_history_free_ _cleanup_(term_history_freep)
336 DEFINE_TRIVIAL_CLEANUP_FUNC(term_history*, term_history_free);
338 void term_history_clear(term_history *history);
339 void term_history_trim(term_history *history, unsigned int max);
340 void term_history_push(term_history *history, term_line *line);
341 term_line *term_history_pop(term_history *history, unsigned int reserve_width, const term_attr *attr, term_age_t age);
342 unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age);
346 * The UTF-decoder and encoder are adjusted for terminals and provide proper
347 * fallbacks for invalid UTF-8. In terminals it's quite usual to use fallbacks
348 * instead of rejecting invalid input. This way, old legacy applications still
349 * work (this is especially important for 7bit/ASCII DEC modes).
356 unsigned int i_bytes : 3;
357 unsigned int n_bytes : 3;
358 unsigned int valid : 1;
361 size_t term_utf8_encode(char *out_utf8, uint32_t g);
362 const uint32_t *term_utf8_decode(term_utf8 *p, size_t *out_len, char c);
366 * The term_parser object parses control-sequences for both host and terminal
367 * side. Based on this parser, there is a set of command-parsers that take a
368 * term_seq sequence and returns the command it represents. This is different
369 * for host and terminal side so a different set of parsers is provided.
373 TERM_SEQ_NONE, /* placeholder, no sequence parsed */
375 TERM_SEQ_IGNORE, /* no-op character */
376 TERM_SEQ_GRAPHIC, /* graphic character */
377 TERM_SEQ_CONTROL, /* control character */
378 TERM_SEQ_ESCAPE, /* escape sequence */
379 TERM_SEQ_CSI, /* control sequence function */
380 TERM_SEQ_DCS, /* device control string */
381 TERM_SEQ_OSC, /* operating system control */
387 /* these must be kept compatible to (1U << (ch - 0x20)) */
389 TERM_SEQ_FLAG_SPACE = (1U << 0), /* char: */
390 TERM_SEQ_FLAG_BANG = (1U << 1), /* char: ! */
391 TERM_SEQ_FLAG_DQUOTE = (1U << 2), /* char: " */
392 TERM_SEQ_FLAG_HASH = (1U << 3), /* char: # */
393 TERM_SEQ_FLAG_CASH = (1U << 4), /* char: $ */
394 TERM_SEQ_FLAG_PERCENT = (1U << 5), /* char: % */
395 TERM_SEQ_FLAG_AND = (1U << 6), /* char: & */
396 TERM_SEQ_FLAG_SQUOTE = (1U << 7), /* char: ' */
397 TERM_SEQ_FLAG_POPEN = (1U << 8), /* char: ( */
398 TERM_SEQ_FLAG_PCLOSE = (1U << 9), /* char: ) */
399 TERM_SEQ_FLAG_MULT = (1U << 10), /* char: * */
400 TERM_SEQ_FLAG_PLUS = (1U << 11), /* char: + */
401 TERM_SEQ_FLAG_COMMA = (1U << 12), /* char: , */
402 TERM_SEQ_FLAG_MINUS = (1U << 13), /* char: - */
403 TERM_SEQ_FLAG_DOT = (1U << 14), /* char: . */
404 TERM_SEQ_FLAG_SLASH = (1U << 15), /* char: / */
406 /* 16-35 is reserved for numbers; unused */
408 /* COLON is reserved = (1U << 26), char: : */
409 /* SEMICOLON is reserved = (1U << 27), char: ; */
410 TERM_SEQ_FLAG_LT = (1U << 28), /* char: < */
411 TERM_SEQ_FLAG_EQUAL = (1U << 29), /* char: = */
412 TERM_SEQ_FLAG_GT = (1U << 30), /* char: > */
413 TERM_SEQ_FLAG_WHAT = (1U << 31), /* char: ? */
417 TERM_CMD_NONE, /* placeholder */
418 TERM_CMD_GRAPHIC, /* graphics character */
420 TERM_CMD_BEL, /* bell */
421 TERM_CMD_BS, /* backspace */
422 TERM_CMD_CBT, /* cursor-backward-tabulation */
423 TERM_CMD_CHA, /* cursor-horizontal-absolute */
424 TERM_CMD_CHT, /* cursor-horizontal-forward-tabulation */
425 TERM_CMD_CNL, /* cursor-next-line */
426 TERM_CMD_CPL, /* cursor-previous-line */
427 TERM_CMD_CR, /* carriage-return */
428 TERM_CMD_CUB, /* cursor-backward */
429 TERM_CMD_CUD, /* cursor-down */
430 TERM_CMD_CUF, /* cursor-forward */
431 TERM_CMD_CUP, /* cursor-position */
432 TERM_CMD_CUU, /* cursor-up */
433 TERM_CMD_DA1, /* primary-device-attributes */
434 TERM_CMD_DA2, /* secondary-device-attributes */
435 TERM_CMD_DA3, /* tertiary-device-attributes */
436 TERM_CMD_DC1, /* device-control-1 */
437 TERM_CMD_DC3, /* device-control-3 */
438 TERM_CMD_DCH, /* delete-character */
439 TERM_CMD_DECALN, /* screen-alignment-pattern */
440 TERM_CMD_DECANM, /* ansi-mode */
441 TERM_CMD_DECBI, /* back-index */
442 TERM_CMD_DECCARA, /* change-attributes-in-rectangular-area */
443 TERM_CMD_DECCRA, /* copy-rectangular-area */
444 TERM_CMD_DECDC, /* delete-column */
445 TERM_CMD_DECDHL_BH, /* double-width-double-height-line: bottom half */
446 TERM_CMD_DECDHL_TH, /* double-width-double-height-line: top half */
447 TERM_CMD_DECDWL, /* double-width-single-height-line */
468 TERM_CMD_DECREQTPARM,
474 TERM_CMD_DECRQM_ANSI,
499 TERM_CMD_DECSLRM_OR_SC,
577 TERM_CMD_XTERM_CLLHP, /* xterm-cursor-lower-left-hp-bugfix */
578 TERM_CMD_XTERM_IHMT, /* xterm-initiate-highlight-mouse-tracking*/
579 TERM_CMD_XTERM_MLHP, /* xterm-memory-lock-hp-bugfix */
580 TERM_CMD_XTERM_MUHP, /* xterm-memory-unlock-hp-bugfix */
581 TERM_CMD_XTERM_RPM, /* xterm-restore-private-mode */
582 TERM_CMD_XTERM_RRV, /* xterm-reset-resource-value */
583 TERM_CMD_XTERM_RTM, /* xterm-reset-title-mode */
584 TERM_CMD_XTERM_SACL1, /* xterm-set-ansi-conformance-level-1 */
585 TERM_CMD_XTERM_SACL2, /* xterm-set-ansi-conformance-level-2 */
586 TERM_CMD_XTERM_SACL3, /* xterm-set-ansi-conformance-level-3 */
587 TERM_CMD_XTERM_SDCS, /* xterm-set-default-character-set */
588 TERM_CMD_XTERM_SGFX, /* xterm-sixel-graphics */
589 TERM_CMD_XTERM_SPM, /* xterm-set-private-mode */
590 TERM_CMD_XTERM_SRV, /* xterm-set-resource-value */
591 TERM_CMD_XTERM_STM, /* xterm-set-title-mode */
592 TERM_CMD_XTERM_SUCS, /* xterm-set-utf8-character-set */
593 TERM_CMD_XTERM_WM, /* xterm-window-management */
600 * Charsets: DEC marks charsets according to "Digital Equ. Corp.".
601 * NRCS marks charsets according to the "National Replacement
602 * Character Sets". ISO marks charsets according to ISO-8859.
603 * The USERDEF charset is special and can be modified by the host.
608 /* 96-compat charsets */
609 TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL,
610 TERM_CHARSET_BRITISH_NRCS = TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL,
611 TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL,
612 TERM_CHARSET_AMERICAN_NRCS = TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL,
613 TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL,
614 TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL,
615 TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL,
616 TERM_CHARSET_ISO_LATIN_CYRILLIC,
620 /* 94-compat charsets */
621 TERM_CHARSET_DEC_SPECIAL_GRAPHIC = TERM_CHARSET_96_CNT,
622 TERM_CHARSET_DEC_SUPPLEMENTAL,
623 TERM_CHARSET_DEC_TECHNICAL,
624 TERM_CHARSET_CYRILLIC_DEC,
625 TERM_CHARSET_DUTCH_NRCS,
626 TERM_CHARSET_FINNISH_NRCS,
627 TERM_CHARSET_FRENCH_NRCS,
628 TERM_CHARSET_FRENCH_CANADIAN_NRCS,
629 TERM_CHARSET_GERMAN_NRCS,
630 TERM_CHARSET_GREEK_DEC,
631 TERM_CHARSET_GREEK_NRCS,
632 TERM_CHARSET_HEBREW_DEC,
633 TERM_CHARSET_HEBREW_NRCS,
634 TERM_CHARSET_ITALIAN_NRCS,
635 TERM_CHARSET_NORWEGIAN_DANISH_NRCS,
636 TERM_CHARSET_PORTUGUESE_NRCS,
637 TERM_CHARSET_RUSSIAN_NRCS,
638 TERM_CHARSET_SCS_NRCS,
639 TERM_CHARSET_SPANISH_NRCS,
640 TERM_CHARSET_SWEDISH_NRCS,
641 TERM_CHARSET_SWISS_NRCS,
642 TERM_CHARSET_TURKISH_DEC,
643 TERM_CHARSET_TURKISH_NRCS,
647 /* special charsets */
648 TERM_CHARSET_USERPREF_SUPPLEMENTAL = TERM_CHARSET_94_CNT,
653 extern term_charset term_unicode_lower;
654 extern term_charset term_unicode_upper;
655 extern term_charset term_dec_supplemental_graphics;
656 extern term_charset term_dec_special_graphics;
658 #define TERM_PARSER_ARG_MAX (16)
659 #define TERM_PARSER_ST_MAX (4096)
663 unsigned int command;
665 unsigned int intermediates;
666 unsigned int charset;
668 int args[TERM_PARSER_ARG_MAX];
681 int term_parser_new(term_parser **out, bool host);
682 term_parser *term_parser_free(term_parser *parser);
683 int term_parser_feed(term_parser *parser, const term_seq **seq_out, uint32_t raw);
685 #define _term_parser_free_ _cleanup_(term_parser_freep)
686 DEFINE_TRIVIAL_CLEANUP_FUNC(term_parser*, term_parser_free);