diff options
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | src/libsystemd-terminal/term-internal.h | 358 | ||||
-rw-r--r-- | src/libsystemd-terminal/term-screen.c | 3882 |
3 files changed, 4110 insertions, 131 deletions
diff --git a/Makefile.am b/Makefile.am index 73f1252d30..0de60148d8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2842,6 +2842,7 @@ libsystemd_terminal_la_SOURCES = \ src/libsystemd-terminal/term-charset.c \ src/libsystemd-terminal/term-page.c \ src/libsystemd-terminal/term-parser.c \ + src/libsystemd-terminal/term-screen.c \ src/libsystemd-terminal/term-wcwidth.c libsystemd_terminal_la_LIBADD = \ diff --git a/src/libsystemd-terminal/term-internal.h b/src/libsystemd-terminal/term-internal.h index a3d1f5458b..345996b943 100644 --- a/src/libsystemd-terminal/term-internal.h +++ b/src/libsystemd-terminal/term-internal.h @@ -42,6 +42,8 @@ typedef struct term_seq term_seq; typedef struct term_parser term_parser; typedef uint32_t term_charset[96]; +typedef struct term_screen term_screen; + /* * Miscellaneous * Sundry things and external helpers. @@ -433,8 +435,8 @@ enum { TERM_CMD_DA1, /* primary-device-attributes */ TERM_CMD_DA2, /* secondary-device-attributes */ TERM_CMD_DA3, /* tertiary-device-attributes */ - TERM_CMD_DC1, /* device-control-1 */ - TERM_CMD_DC3, /* device-control-3 */ + TERM_CMD_DC1, /* device-control-1 or XON */ + TERM_CMD_DC3, /* device-control-3 or XOFF */ TERM_CMD_DCH, /* delete-character */ TERM_CMD_DECALN, /* screen-alignment-pattern */ TERM_CMD_DECANM, /* ansi-mode */ @@ -445,135 +447,135 @@ enum { TERM_CMD_DECDHL_BH, /* double-width-double-height-line: bottom half */ TERM_CMD_DECDHL_TH, /* double-width-double-height-line: top half */ TERM_CMD_DECDWL, /* double-width-single-height-line */ - TERM_CMD_DECEFR, - TERM_CMD_DECELF, - TERM_CMD_DECELR, - TERM_CMD_DECERA, - TERM_CMD_DECFI, - TERM_CMD_DECFRA, - TERM_CMD_DECIC, - TERM_CMD_DECID, - TERM_CMD_DECINVM, - TERM_CMD_DECKBD, - TERM_CMD_DECKPAM, - TERM_CMD_DECKPNM, - TERM_CMD_DECLFKC, - TERM_CMD_DECLL, - TERM_CMD_DECLTOD, - TERM_CMD_DECPCTERM, - TERM_CMD_DECPKA, - TERM_CMD_DECPKFMR, - TERM_CMD_DECRARA, - TERM_CMD_DECRC, - TERM_CMD_DECREQTPARM, - TERM_CMD_DECRPKT, - TERM_CMD_DECRQCRA, - TERM_CMD_DECRQDE, - TERM_CMD_DECRQKT, - TERM_CMD_DECRQLP, - TERM_CMD_DECRQM_ANSI, - TERM_CMD_DECRQM_DEC, - TERM_CMD_DECRQPKFM, - TERM_CMD_DECRQPSR, - TERM_CMD_DECRQTSR, - TERM_CMD_DECRQUPSS, - TERM_CMD_DECSACE, - TERM_CMD_DECSASD, - TERM_CMD_DECSC, - TERM_CMD_DECSCA, - TERM_CMD_DECSCL, - TERM_CMD_DECSCP, - TERM_CMD_DECSCPP, - TERM_CMD_DECSCS, - TERM_CMD_DECSCUSR, - TERM_CMD_DECSDDT, - TERM_CMD_DECSDPT, - TERM_CMD_DECSED, - TERM_CMD_DECSEL, - TERM_CMD_DECSERA, - TERM_CMD_DECSFC, - TERM_CMD_DECSKCV, - TERM_CMD_DECSLCK, - TERM_CMD_DECSLE, - TERM_CMD_DECSLPP, - TERM_CMD_DECSLRM_OR_SC, - TERM_CMD_DECSMBV, - TERM_CMD_DECSMKR, - TERM_CMD_DECSNLS, - TERM_CMD_DECSPP, - TERM_CMD_DECSPPCS, - TERM_CMD_DECSPRTT, - TERM_CMD_DECSR, - TERM_CMD_DECSRFR, - TERM_CMD_DECSSCLS, - TERM_CMD_DECSSDT, - TERM_CMD_DECSSL, - TERM_CMD_DECST8C, - TERM_CMD_DECSTBM, - TERM_CMD_DECSTR, - TERM_CMD_DECSTRL, - TERM_CMD_DECSWBV, - TERM_CMD_DECSWL, - TERM_CMD_DECTID, - TERM_CMD_DECTME, - TERM_CMD_DECTST, - TERM_CMD_DL, - TERM_CMD_DSR_ANSI, - TERM_CMD_DSR_DEC, - TERM_CMD_ECH, - TERM_CMD_ED, - TERM_CMD_EL, - TERM_CMD_ENQ, - TERM_CMD_EPA, - TERM_CMD_FF, - TERM_CMD_HPA, - TERM_CMD_HPR, - TERM_CMD_HT, - TERM_CMD_HTS, - TERM_CMD_HVP, - TERM_CMD_ICH, - TERM_CMD_IL, - TERM_CMD_IND, - TERM_CMD_LF, - TERM_CMD_LS1R, - TERM_CMD_LS2, - TERM_CMD_LS2R, - TERM_CMD_LS3, - TERM_CMD_LS3R, - TERM_CMD_MC_ANSI, - TERM_CMD_MC_DEC, - TERM_CMD_NEL, - TERM_CMD_NP, - TERM_CMD_NULL, - TERM_CMD_PP, - TERM_CMD_PPA, - TERM_CMD_PPB, - TERM_CMD_PPR, - TERM_CMD_RC, - TERM_CMD_REP, - TERM_CMD_RI, - TERM_CMD_RIS, - TERM_CMD_RM_ANSI, - TERM_CMD_RM_DEC, - TERM_CMD_S7C1T, - TERM_CMD_S8C1T, - TERM_CMD_SCS, - TERM_CMD_SD, - TERM_CMD_SGR, - TERM_CMD_SI, - TERM_CMD_SM_ANSI, - TERM_CMD_SM_DEC, - TERM_CMD_SO, - TERM_CMD_SPA, - TERM_CMD_SS2, - TERM_CMD_SS3, - TERM_CMD_ST, - TERM_CMD_SU, - TERM_CMD_SUB, - TERM_CMD_TBC, - TERM_CMD_VPA, - TERM_CMD_VPR, - TERM_CMD_VT, + TERM_CMD_DECEFR, /* enable-filter-rectangle */ + TERM_CMD_DECELF, /* enable-local-functions */ + TERM_CMD_DECELR, /* enable-locator-reporting */ + TERM_CMD_DECERA, /* erase-rectangular-area */ + TERM_CMD_DECFI, /* forward-index */ + TERM_CMD_DECFRA, /* fill-rectangular-area */ + TERM_CMD_DECIC, /* insert-column */ + TERM_CMD_DECID, /* return-terminal-id */ + TERM_CMD_DECINVM, /* invoke-macro */ + TERM_CMD_DECKBD, /* keyboard-language-selection */ + TERM_CMD_DECKPAM, /* keypad-application-mode */ + TERM_CMD_DECKPNM, /* keypad-numeric-mode */ + TERM_CMD_DECLFKC, /* local-function-key-control */ + TERM_CMD_DECLL, /* load-leds */ + TERM_CMD_DECLTOD, /* load-time-of-day */ + TERM_CMD_DECPCTERM, /* pcterm-mode */ + TERM_CMD_DECPKA, /* program-key-action */ + TERM_CMD_DECPKFMR, /* program-key-free-memory-report */ + TERM_CMD_DECRARA, /* reverse-attributes-in-rectangular-area */ + TERM_CMD_DECRC, /* restore-cursor */ + TERM_CMD_DECREQTPARM, /* request-terminal-parameters */ + TERM_CMD_DECRPKT, /* report-key-type */ + TERM_CMD_DECRQCRA, /* request-checksum-of-rectangular-area */ + TERM_CMD_DECRQDE, /* request-display-extent */ + TERM_CMD_DECRQKT, /* request-key-type */ + TERM_CMD_DECRQLP, /* request-locator-position */ + TERM_CMD_DECRQM_ANSI, /* request-mode-ansi */ + TERM_CMD_DECRQM_DEC, /* request-mode-dec */ + TERM_CMD_DECRQPKFM, /* request-program-key-free-memory */ + TERM_CMD_DECRQPSR, /* request-presentation-state-report */ + TERM_CMD_DECRQTSR, /* request-terminal-state-report */ + TERM_CMD_DECRQUPSS, /* request-user-preferred-supplemental-set */ + TERM_CMD_DECSACE, /* select-attribute-change-extent */ + TERM_CMD_DECSASD, /* select-active-status-display */ + TERM_CMD_DECSC, /* save-cursor */ + TERM_CMD_DECSCA, /* select-character-protection-attribute */ + TERM_CMD_DECSCL, /* select-conformance-level */ + TERM_CMD_DECSCP, /* select-communication-port */ + TERM_CMD_DECSCPP, /* select-columns-per-page */ + TERM_CMD_DECSCS, /* select-communication-speed */ + TERM_CMD_DECSCUSR, /* set-cursor-style */ + TERM_CMD_DECSDDT, /* select-disconnect-delay-time */ + TERM_CMD_DECSDPT, /* select-digital-printed-data-type */ + TERM_CMD_DECSED, /* selective-erase-in-display */ + TERM_CMD_DECSEL, /* selective-erase-in-line */ + TERM_CMD_DECSERA, /* selective-erase-rectangular-area */ + TERM_CMD_DECSFC, /* select-flow-control */ + TERM_CMD_DECSKCV, /* set-key-click-volume */ + TERM_CMD_DECSLCK, /* set-lock-key-style */ + TERM_CMD_DECSLE, /* select-locator-events */ + TERM_CMD_DECSLPP, /* set-lines-per-page */ + TERM_CMD_DECSLRM_OR_SC, /* set-left-and-right-margins or save-cursor */ + TERM_CMD_DECSMBV, /* set-margin-bell-volume */ + TERM_CMD_DECSMKR, /* select-modifier-key-reporting */ + TERM_CMD_DECSNLS, /* set-lines-per-screen */ + TERM_CMD_DECSPP, /* set-port-parameter */ + TERM_CMD_DECSPPCS, /* select-pro-printer-character-set */ + TERM_CMD_DECSPRTT, /* select-printer-type */ + TERM_CMD_DECSR, /* secure-reset */ + TERM_CMD_DECSRFR, /* select-refresh-rate */ + TERM_CMD_DECSSCLS, /* set-scroll-speed */ + TERM_CMD_DECSSDT, /* select-status-display-line-type */ + TERM_CMD_DECSSL, /* select-setup-language */ + TERM_CMD_DECST8C, /* set-tab-at-every-8-columns */ + TERM_CMD_DECSTBM, /* set-top-and-bottom-margins */ + TERM_CMD_DECSTR, /* soft-terminal-reset */ + TERM_CMD_DECSTRL, /* set-transmit-rate-limit */ + TERM_CMD_DECSWBV, /* set-warning-bell-volume */ + TERM_CMD_DECSWL, /* single-width-single-height-line */ + TERM_CMD_DECTID, /* select-terminal-id */ + TERM_CMD_DECTME, /* terminal-mode-emulation */ + TERM_CMD_DECTST, /* invoke-confidence-test */ + TERM_CMD_DL, /* delete-line */ + TERM_CMD_DSR_ANSI, /* device-status-report-ansi */ + TERM_CMD_DSR_DEC, /* device-status-report-dec */ + TERM_CMD_ECH, /* erase-character */ + TERM_CMD_ED, /* erase-in-display */ + TERM_CMD_EL, /* erase-in-line */ + TERM_CMD_ENQ, /* enquiry */ + TERM_CMD_EPA, /* end-of-guarded-area */ + TERM_CMD_FF, /* form-feed */ + TERM_CMD_HPA, /* horizontal-position-absolute */ + TERM_CMD_HPR, /* horizontal-position-relative */ + TERM_CMD_HT, /* horizontal-tab */ + TERM_CMD_HTS, /* horizontal-tab-set */ + TERM_CMD_HVP, /* horizontal-and-vertical-position */ + TERM_CMD_ICH, /* insert-character */ + TERM_CMD_IL, /* insert-line */ + TERM_CMD_IND, /* index */ + TERM_CMD_LF, /* line-feed */ + TERM_CMD_LS1R, /* locking-shift-1-right */ + TERM_CMD_LS2, /* locking-shift-2 */ + TERM_CMD_LS2R, /* locking-shift-2-right */ + TERM_CMD_LS3, /* locking-shift-3 */ + TERM_CMD_LS3R, /* locking-shift-3-right */ + TERM_CMD_MC_ANSI, /* media-copy-ansi */ + TERM_CMD_MC_DEC, /* media-copy-dec */ + TERM_CMD_NEL, /* next-line */ + TERM_CMD_NP, /* next-page */ + TERM_CMD_NULL, /* null */ + TERM_CMD_PP, /* preceding-page */ + TERM_CMD_PPA, /* page-position-absolute */ + TERM_CMD_PPB, /* page-position-backward */ + TERM_CMD_PPR, /* page-position-relative */ + TERM_CMD_RC, /* restore-cursor */ + TERM_CMD_REP, /* repeat */ + TERM_CMD_RI, /* reverse-index */ + TERM_CMD_RIS, /* reset-to-initial-state */ + TERM_CMD_RM_ANSI, /* reset-mode-ansi */ + TERM_CMD_RM_DEC, /* reset-mode-dec */ + TERM_CMD_S7C1T, /* set-7bit-c1-terminal */ + TERM_CMD_S8C1T, /* set-8bit-c1-terminal */ + TERM_CMD_SCS, /* select-character-set */ + TERM_CMD_SD, /* scroll-down */ + TERM_CMD_SGR, /* select-graphics-rendition */ + TERM_CMD_SI, /* shift-in */ + TERM_CMD_SM_ANSI, /* set-mode-ansi */ + TERM_CMD_SM_DEC, /* set-mode-dec */ + TERM_CMD_SO, /* shift-out */ + TERM_CMD_SPA, /* start-of-protected-area */ + TERM_CMD_SS2, /* single-shift-2 */ + TERM_CMD_SS3, /* single-shift-3 */ + TERM_CMD_ST, /* string-terminator */ + TERM_CMD_SU, /* scroll-up */ + TERM_CMD_SUB, /* substitute */ + TERM_CMD_TBC, /* tab-clear */ + TERM_CMD_VPA, /* vertical-line-position-absolute */ + TERM_CMD_VPR, /* vertical-line-position-relative */ + TERM_CMD_VT, /* vertical-tab */ TERM_CMD_XTERM_CLLHP, /* xterm-cursor-lower-left-hp-bugfix */ TERM_CMD_XTERM_IHMT, /* xterm-initiate-highlight-mouse-tracking*/ TERM_CMD_XTERM_MLHP, /* xterm-memory-lock-hp-bugfix */ @@ -684,3 +686,97 @@ int term_parser_feed(term_parser *parser, const term_seq **seq_out, uint32_t raw #define _term_parser_free_ _cleanup_(term_parser_freep) DEFINE_TRIVIAL_CLEANUP_FUNC(term_parser*, term_parser_free); + +/* + * Screens + * A term_screen object represents the terminal-side of the communication. It + * connects the term-parser and term-pages and handles all required commands. + * All state is managed by it. + */ + +enum { + TERM_FLAG_7BIT_MODE = (1U << 0), /* 7bit mode (default: off) */ + TERM_FLAG_HIDE_CURSOR = (1U << 1), /* hide cursor caret (default: off) */ + TERM_FLAG_INHIBIT_TPARM = (1U << 2), /* do not send TPARM unrequested (default: off) */ + TERM_FLAG_NEWLINE_MODE = (1U << 3), /* perform carriage-return on line-feeds (default: off) */ + TERM_FLAG_ORIGIN_MODE = (1U << 4), /* in origin mode, the cursor is bound by the margins (default: off) */ + TERM_FLAG_PENDING_WRAP = (1U << 5), /* wrap-around is pending */ + TERM_FLAG_AUTO_WRAP = (1U << 6), /* auto-wrap mode causes line-wraps at line-ends (default: off) */ + TERM_FLAG_KEYPAD_MODE = (1U << 7), /* application-keypad mode (default: off) */ + TERM_FLAG_CURSOR_KEYS = (1U << 8), /* enable application cursor-keys (default: off) */ +}; + +enum { + TERM_CONFORMANCE_LEVEL_VT52, + TERM_CONFORMANCE_LEVEL_VT100, + TERM_CONFORMANCE_LEVEL_VT400, + TERM_CONFORMANCE_LEVEL_CNT, +}; + +typedef int (*term_screen_write_fn) (term_screen *screen, void *userdata, const void *buf, size_t size); +typedef int (*term_screen_cmd_fn) (term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq); + +struct term_screen { + unsigned long ref; + term_age_t age; + + term_page *page; + term_page *page_main; + term_page *page_alt; + term_history *history; + term_history *history_main; + + unsigned int n_tabs; + uint8_t *tabs; + + term_utf8 utf8; + term_parser *parser; + + term_screen_write_fn write_fn; + void *write_fn_data; + term_screen_cmd_fn cmd_fn; + void *cmd_fn_data; + + unsigned int flags; + unsigned int conformance_level; + unsigned int cursor_x; + unsigned int cursor_y; + term_attr attr; + term_attr default_attr; + + term_charset **gl; + term_charset **gr; + term_charset **glt; + term_charset **grt; + term_charset *g0; + term_charset *g1; + term_charset *g2; + term_charset *g3; + + char *answerback; + + struct { + unsigned int cursor_x; + unsigned int cursor_y; + term_attr attr; + term_charset **gl; + term_charset **gr; + term_charset **glt; + term_charset **grt; + unsigned int flags; + } saved; +}; + +int term_screen_new(term_screen **out, term_screen_write_fn write_fn, void *write_fn_data, term_screen_cmd_fn cmd_fn, void *cmd_fn_data); +term_screen *term_screen_ref(term_screen *screen); +term_screen *term_screen_unref(term_screen *screen); + +DEFINE_TRIVIAL_CLEANUP_FUNC(term_screen*, term_screen_unref); + +int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size); +int term_screen_feed_keyboard(term_screen *screen, uint32_t keysym, uint32_t ascii, uint32_t ucs4, unsigned int mods); +int term_screen_resize(term_screen *screen, unsigned int width, unsigned int height); +void term_screen_soft_reset(term_screen *screen); +void term_screen_hard_reset(term_screen *screen); + +int term_screen_set_answerback(term_screen *screen, const char *answerback); diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c new file mode 100644 index 0000000000..a19c684d2a --- /dev/null +++ b/src/libsystemd-terminal/term-screen.c @@ -0,0 +1,3882 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* + * Terminal Screens + * The term_screen layer implements the terminal-side. It handles all commands + * returned by the seq-parser and applies them to its own pages. + * + * While there are a lot of legacy control-sequences, we only support a small + * subset. There is no reason to implement unused codes like horizontal + * scrolling. + * If you implement new commands, make sure to document them properly. + * + * Standards: + * ECMA-48 + * ANSI X3.64 + * ISO/IEC 6429 + * References: + * http://www.vt100.net/emu/ctrlseq_dec.html + * http://www.vt100.net/docs/vt100-ug/chapter3.html + * http://www.vt100.net/docs/vt510-rm/chapter4 + * http://www.vt100.net/docs/vt510-rm/contents + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * ASCII + * http://en.wikipedia.org/wiki/C0_and_C1_control_codes + * https://en.wikipedia.org/wiki/ANSI_color + */ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include "macro.h" +#include "term-internal.h" +#include "util.h" + +int term_screen_new(term_screen **out, term_screen_write_fn write_fn, void *write_fn_data, term_screen_cmd_fn cmd_fn, void *cmd_fn_data) { + _cleanup_(term_screen_unrefp) term_screen *screen = NULL; + int r; + + assert_return(out, -EINVAL); + + screen = new0(term_screen, 1); + if (!screen) + return -ENOMEM; + + screen->ref = 1; + screen->age = 1; + screen->write_fn = write_fn; + screen->write_fn_data = write_fn_data; + screen->cmd_fn = cmd_fn; + screen->cmd_fn_data = cmd_fn_data; + screen->flags = TERM_FLAG_7BIT_MODE; + screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400; + screen->gl = &screen->g0; + screen->gr = &screen->g1; + screen->g0 = &term_unicode_lower; + screen->g1 = &term_unicode_upper; + screen->g2 = &term_unicode_lower; + screen->g3 = &term_unicode_upper; + + screen->saved.cursor_x = 0; + screen->saved.cursor_y = 0; + screen->saved.attr = screen->attr; + screen->saved.gl = screen->gl; + screen->saved.gr = screen->gr; + + r = term_page_new(&screen->page_main); + if (r < 0) + return r; + + r = term_page_new(&screen->page_alt); + if (r < 0) + return r; + + r = term_parser_new(&screen->parser, false); + if (r < 0) + return r; + + r = term_history_new(&screen->history_main); + if (r < 0) + return r; + + screen->page = screen->page_main; + screen->history = screen->history_main; + + *out = screen; + screen = NULL; + return 0; +} + +term_screen *term_screen_ref(term_screen *screen) { + if (!screen) + return NULL; + + assert_return(screen->ref > 0, NULL); + + ++screen->ref; + return screen; +} + +term_screen *term_screen_unref(term_screen *screen) { + if (!screen) + return NULL; + + assert_return(screen->ref > 0, NULL); + + if (--screen->ref) + return NULL; + + free(screen->answerback); + free(screen->tabs); + term_history_free(screen->history_main); + term_page_free(screen->page_alt); + term_page_free(screen->page_main); + term_parser_free(screen->parser); + free(screen); + + return NULL; +} + +/* + * Write-Helpers + * Unfortunately, 7bit/8bit compat mode requires us to send C1 controls encoded + * as 7bit if asked by the application. This is really used in the wild, so we + * cannot fall back to "always 7bit". + * screen_write() is the underlying backend which forwards any writes to the + * users's callback. It's the users responsibility to buffer these and write + * them out once their call to term_screen_feed_*() returns. + * The SEQ_WRITE() and SEQ_WRITE_KEY() macros allow constructing C0/C1 sequences + * directly in the code-base without requiring any intermediate buffer during + * runtime. + */ + +#define C0_CSI "\e[" +#define C1_CSI "\x9b" + +#define SEQ(_screen, _prefix_esc, _c0, _c1, _seq) \ + (((_screen)->flags & TERM_FLAG_7BIT_MODE) ? \ + ((_prefix_esc) ? ("\e" _c0 _seq) : (_c0 _seq)) : \ + ((_prefix_esc) ? ("\e" _c1 _seq) : (_c1 _seq))) + +#define SEQ_SIZE(_screen, _prefix_esc, _c0, _c1, _seq) \ + (((_screen)->flags & TERM_FLAG_7BIT_MODE) ? \ + ((_prefix_esc) ? sizeof("\e" _c0 _seq) : sizeof(_c0 _seq)) : \ + ((_prefix_esc) ? sizeof("\e" _c1 _seq) : sizeof(_c1 _seq))) + +#define SEQ_WRITE_KEY(_screen, _prefix_esc, _c0, _c1, _seq) \ + screen_write((_screen), \ + SEQ((_screen), (_prefix_esc), \ + _c0, _c1, _seq), \ + SEQ_SIZE((_screen), (_prefix_esc), \ + _c0, _c1, _seq) - 1) + +#define SEQ_WRITE(_screen, _c0, _c1, _seq) \ + SEQ_WRITE_KEY((_screen), false, _c0, _c1, _seq) + +static int screen_write(term_screen *screen, const void *buf, size_t len) { + if (len < 1 || !screen->write_fn) + return 0; + + return screen->write_fn(screen, screen->write_fn_data, buf, len); +} + +/* + * Command Forwarding + * Some commands cannot be handled by the screen-layer directly. Those are + * forwarded to the command-handler of the caller. This is rarely used and can + * safely be set to NULL. + */ + +static int screen_forward(term_screen *screen, unsigned int cmd, const term_seq *seq) { + if (!screen->cmd_fn) + return 0; + + return screen->cmd_fn(screen, screen->cmd_fn_data, cmd, seq); +} + +/* + * Screen Helpers + * These helpers implement common-operations like cursor-handler and more, which + * are used by several command dispatchers. + */ + +static unsigned int screen_clamp_x(term_screen *screen, unsigned int x) { + if (x >= screen->page->width) + return (screen->page->width > 0) ? screen->page->width - 1 : 0; + + return x; +} + +static unsigned int screen_clamp_y(term_screen *screen, unsigned int y) { + if (y >= screen->page->height) + return (screen->page->height > 0) ? screen->page->height - 1 : 0; + + return y; +} + +static bool screen_tab_is_set(term_screen *screen, unsigned int pos) { + if (pos >= screen->page->width) + return false; + + return screen->tabs[pos / 8] & (1 << (pos % 8)); +} + +static inline void screen_age_cursor(term_screen *screen) { + term_cell *cell; + + cell = term_page_get_cell(screen->page, screen->cursor_x, screen->cursor_y); + if (cell) + cell->age = screen->age; +} + +static void screen_cursor_clear_wrap(term_screen *screen) { + screen->flags &= ~TERM_FLAG_PENDING_WRAP; +} + +static void screen_cursor_set(term_screen *screen, unsigned int x, unsigned int y) { + x = screen_clamp_x(screen, x); + y = screen_clamp_y(screen, y); + + if (x == screen->cursor_x && y == screen->cursor_y) + return; + + if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) + screen_age_cursor(screen); + + screen->cursor_x = x; + screen->cursor_y = y; + + if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) + screen_age_cursor(screen); +} + +static void screen_cursor_set_rel(term_screen *screen, unsigned int x, unsigned int y) { + if (screen->flags & TERM_FLAG_ORIGIN_MODE) { + x = screen_clamp_x(screen, x); + y = screen_clamp_x(screen, y) + screen->page->scroll_idx; + + if (y >= screen->page->scroll_idx + screen->page->scroll_num) { + y = screen->page->scroll_idx + screen->page->scroll_num; + if (screen->page->scroll_num > 0) + y -= 1; + } + } + + screen_cursor_set(screen, x, y); +} + +static void screen_cursor_left(term_screen *screen, unsigned int num) { + if (num > screen->cursor_x) + num = screen->cursor_x; + + screen_cursor_set(screen, screen->cursor_x - num, screen->cursor_y); +} + +static void screen_cursor_left_tab(term_screen *screen, unsigned int num) { + unsigned int i; + + i = screen->cursor_x; + while (i > 0 && num > 0) { + if (screen_tab_is_set(screen, --i)) + --num; + } + + screen_cursor_set(screen, i, screen->cursor_y); +} + +static void screen_cursor_right(term_screen *screen, unsigned int num) { + if (num > screen->page->width) + num = screen->page->width; + + screen_cursor_set(screen, screen->cursor_x + num, screen->cursor_y); +} + +static void screen_cursor_right_tab(term_screen *screen, unsigned int num) { + unsigned int i; + + i = screen->cursor_x; + while (i + 1 < screen->page->width && num > 0) { + if (screen_tab_is_set(screen, ++i)) + --num; + } + + screen_cursor_set(screen, i, screen->cursor_y); +} + +static void screen_cursor_up(term_screen *screen, unsigned int num, bool scroll) { + unsigned int max; + + if (screen->cursor_y < screen->page->scroll_idx) { + if (num > screen->cursor_y) + num = screen->cursor_y; + + screen_cursor_set(screen, screen->cursor_x, screen->cursor_y - num); + } else { + max = screen->cursor_y - screen->page->scroll_idx; + if (num > max) { + if (num < 1) + return; + + if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) + screen_age_cursor(screen); + + if (scroll) + term_page_scroll_down(screen->page, num - max, &screen->attr, screen->age, NULL); + + screen->cursor_y = screen->page->scroll_idx; + + if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) + screen_age_cursor(screen); + } else { + screen_cursor_set(screen, screen->cursor_x, screen->cursor_y - num); + } + } +} + +static void screen_cursor_down(term_screen *screen, unsigned int num, bool scroll) { + unsigned int max; + + if (screen->cursor_y >= screen->page->scroll_idx + screen->page->scroll_num) { + if (num > screen->page->height) + num = screen->page->height; + + screen_cursor_set(screen, screen->cursor_x, screen->cursor_y - num); + } else { + max = screen->page->scroll_idx + screen->page->scroll_num - 1 - screen->cursor_y; + if (num > max) { + if (num < 1) + return; + + if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) + screen_age_cursor(screen); + + if (scroll) + term_page_scroll_up(screen->page, num - max, &screen->attr, screen->age, screen->history); + + screen->cursor_y = screen->page->scroll_idx + screen->page->scroll_num - 1; + + if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) + screen_age_cursor(screen); + } else { + screen_cursor_set(screen, screen->cursor_x, screen->cursor_y + num); + } + } +} + +static inline void set_reset(term_screen *screen, unsigned int flag, bool set) { + if (set) + screen->flags |= flag; + else + screen->flags &= ~flag; +} + +static void screen_mode_change(term_screen *screen, unsigned int mode, bool dec, bool set) { + switch (mode) { + case 1: + if (dec) { + /* + * DECCKM: cursor-keys + * TODO + */ + set_reset(screen, TERM_FLAG_CURSOR_KEYS, set); + } + + break; + case 6: + if (dec) { + /* + * DECOM: origin-mode + * TODO + */ + set_reset(screen, TERM_FLAG_ORIGIN_MODE, set); + } + + break; + case 7: + if (dec) { + /* + * DECAWN: auto-wrap mode + * TODO + */ + set_reset(screen, TERM_FLAG_AUTO_WRAP, set); + } + + break; + case 20: + if (!dec) { + /* + * LNM: line-feed/new-line mode + * TODO + */ + set_reset(screen, TERM_FLAG_NEWLINE_MODE, set); + } + + break; + case 25: + if (dec) { + /* + * DECTCEM: text-cursor-enable + * TODO + */ + set_reset(screen, TERM_FLAG_HIDE_CURSOR, !set); + } + + break; + } +} + +/* map a character according to current GL and GR maps */ +static uint32_t screen_map(term_screen *screen, uint32_t val) { + uint32_t nval = -1U; + + /* 32 and 127 always map to identity. 160 and 255 map to identity iff a + * 96 character set is loaded into GR. Values above 255 always map to + * identity. */ + switch (val) { + case 33 ... 126: + if (screen->glt) { + nval = (**screen->glt)[val - 32]; + screen->glt = NULL; + } else { + nval = (**screen->gl)[val - 32]; + } + break; + case 160 ... 255: + if (screen->grt) { + nval = (**screen->grt)[val - 160]; + screen->grt = NULL; + } else { + nval = (**screen->gr)[val - 160]; + } + break; + } + + return (nval == -1U) ? val : nval; +} + +/* + * Command Handlers + * This is the inofficial documentation of all the TERM_CMD_* definitions. Each + * handled command has a separate function with an extensive comment on the + * semantics of the command. + * Note that many semantics are unknown and need to be verified. This is mostly + * about error-handling, though. Applications rarely rely on those features. + */ + +static int screen_DA1(term_screen *screen, const term_seq *seq); +static int screen_LF(term_screen *screen, const term_seq *seq); + +static int screen_GRAPHIC(term_screen *screen, const term_seq *seq) { + term_char_t ch = TERM_CHAR_NULL; + uint32_t c; + + if (screen->cursor_x + 1 == screen->page->width + && screen->flags & TERM_FLAG_PENDING_WRAP + && screen->flags & TERM_FLAG_AUTO_WRAP) { + screen_cursor_down(screen, 1, true); + screen_cursor_set(screen, 0, screen->cursor_y); + } + + screen_cursor_clear_wrap(screen); + + c = screen_map(screen, seq->terminator); + ch = term_char_merge(ch, screen_map(screen, c)); + term_page_write(screen->page, screen->cursor_x, screen->cursor_y, ch, 1, &screen->attr, screen->age, false); + + if (screen->cursor_x + 1 == screen->page->width) + screen->flags |= TERM_FLAG_PENDING_WRAP; + else + screen_cursor_right(screen, 1); + + return 0; +} + +static int screen_BEL(term_screen *screen, const term_seq *seq) { + /* + * BEL - sound bell tone + * This command should trigger an acoustic bell. Usually, this is + * forwarded directly to the pcspkr. However, bells have become quite + * uncommon and annoying, so we're not implementing them here. Instead, + * it's one of the commands we forward to the caller. + */ + + return screen_forward(screen, TERM_CMD_BEL, seq); +} + +static int screen_BS(term_screen *screen, const term_seq *seq) { + /* + * BS - backspace + * Move cursor one cell to the left. If already at the left margin, + * nothing happens. + */ + + screen_cursor_clear_wrap(screen); + screen_cursor_left(screen, 1); + return 0; +} + +static int screen_CBT(term_screen *screen, const term_seq *seq) { + /* + * CBT - cursor-backward-tabulation + * Move the cursor @args[0] tabs backwards (to the left). The + * current cursor cell, in case it's a tab, is not counted. + * Furthermore, the cursor cannot be moved beyond position 0 and + * it will stop there. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_left_tab(screen, num); + + return 0; +} + +static int screen_CHA(term_screen *screen, const term_seq *seq) { + /* + * CHA - cursor-horizontal-absolute + * Move the cursor to position @args[0] in the current line. The + * cursor cannot be moved beyond the rightmost cell and will stop + * there. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int pos = 1; + + if (seq->args[0] > 0) + pos = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_set(screen, pos - 1, screen->cursor_y); + + return 0; +} + +static int screen_CHT(term_screen *screen, const term_seq *seq) { + /* + * CHT - cursor-horizontal-forward-tabulation + * Move the cursor @args[0] tabs forward (to the right). The + * current cursor cell, in case it's a tab, is not counted. + * Furthermore, the cursor cannot be moved beyond the rightmost cell + * and will stop there. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_right_tab(screen, num); + + return 0; +} + +static int screen_CNL(term_screen *screen, const term_seq *seq) { + /* + * CNL - cursor-next-line + * Move the cursor @args[0] lines down. + * + * TODO: Does this stop at the bottom or cause a scroll-up? + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_down(screen, num, false); + + return 0; +} + +static int screen_CPL(term_screen *screen, const term_seq *seq) { + /* + * CPL - cursor-preceding-line + * Move the cursor @args[0] lines up. + * + * TODO: Does this stop at the top or cause a scroll-up? + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_up(screen, num, false); + + return 0; +} + +static int screen_CR(term_screen *screen, const term_seq *seq) { + /* + * CR - carriage-return + * Move the cursor to the left margin on the current line. + */ + + screen_cursor_clear_wrap(screen); + screen_cursor_set(screen, 0, screen->cursor_y); + + return 0; +} + +static int screen_CUB(term_screen *screen, const term_seq *seq) { + /* + * CUB - cursor-backward + * Move the cursor @args[0] positions to the left. The cursor stops + * at the left-most position. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_left(screen, num); + + return 0; +} + +static int screen_CUD(term_screen *screen, const term_seq *seq) { + /* + * CUD - cursor-down + * Move the cursor @args[0] positions down. The cursor stops at the + * bottom margin. If it was already moved further, it stops at the + * bottom line. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_down(screen, num, false); + + return 0; +} + +static int screen_CUF(term_screen *screen, const term_seq *seq) { + /* + * CUF -cursor-forward + * Move the cursor @args[0] positions to the right. The cursor stops + * at the right-most position. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_right(screen, num); + + return 0; +} + +static int screen_CUP(term_screen *screen, const term_seq *seq) { + /* + * CUP - cursor-position + * Moves the cursor to position @args[1] x @args[0]. If either is 0, it + * is treated as 1. The positions are subject to the origin-mode and + * clamped to the addressable with/height. + * + * Defaults: + * args[0]: 1 + * args[1]: 1 + */ + + unsigned int x = 1, y = 1; + + if (seq->args[0] > 0) + y = seq->args[0]; + if (seq->args[1] > 0) + x = seq->args[1]; + + screen_cursor_clear_wrap(screen); + screen_cursor_set_rel(screen, x - 1, y - 1); + + return 0; +} + +static int screen_CUU(term_screen *screen, const term_seq *seq) { + /* + * CUU - cursor-up + * Move the cursor @args[0] positions up. The cursor stops at the + * top margin. If it was already moved further, it stops at the + * top line. + * + * Defaults: + * args[0]: 1 + * + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_up(screen, num, false); + + return 0; +} + +static int screen_DA1(term_screen *screen, const term_seq *seq) { + /* + * DA1 - primary-device-attributes + * The primary DA asks for basic terminal features. We simply return + * a hard-coded list of features we implement. + * Note that the primary DA asks for supported features, not currently + * enabled features. + * + * The terminal's answer is: + * ^[ ? 64 ; ARGS c + * The first argument, 64, is fixed and denotes a VT420, the last + * DEC-term that extended this number. + * All following arguments denote supported features. Note + * that at most 15 features can be sent (max CSI args). It is safe to + * send more, but clients might not be able to parse them. This is a + * client's problem and we shouldn't care. There is no other way to + * send those feature lists, so we have to extend them beyond 15 in + * those cases. + * + * Known modes: + * 1: 132 column mode + * The 132 column mode is supported by the terminal. + * 2: printer port + * A priner-port is supported and can be addressed via + * control-codes. + * 3: ReGIS graphics + * Support for ReGIS graphics is available. The ReGIS routines + * provide the "remote graphics instruction set" and allow basic + * vector-rendering. + * 4: sixel + * Support of Sixel graphics is available. This provides access + * to the sixel bitmap routines. + * 6: selective erase + * The terminal supports DECSCA and related selective-erase + * functions. This allows to protect specific cells from being + * erased, if specified. + * 7: soft character set (DRCS) + * TODO: ? + * 8: user-defined keys (UDKs) + * TODO: ? + * 9: national-replacement character sets (NRCS) + * National-replacement character-sets are available. + * 12: Yugoslavian (SCS) + * TODO: ? + * 15: technical character set + * The DEC technical-character-set is available. + * 18: windowing capability + * TODO: ? + * 21: horizontal scrolling + * TODO: ? + * 22: ANSII color + * TODO: ? + * 23: Greek + * TODO: ? + * 24: Turkish + * TODO: ? + * 29: ANSI text locator + * TODO: ? + * 42: ISO Latin-2 character set + * TODO: ? + * 44: PCTerm + * TODO: ? + * 45: soft keymap + * TODO: ? + * 46: ASCII emulation + * TODO: ? + */ + + return SEQ_WRITE(screen, C0_CSI, C1_CSI, "?64;1;6;9;15c"); +} + +static int screen_DA2(term_screen *screen, const term_seq *seq) { + /* + * DA2 - secondary-device-attributes + * The secondary DA asks for the terminal-ID, firmware versions and + * other non-primary attributes. All these values are + * informational-only and should not be used by the host to detect + * terminal features. + * + * The terminal's response is: + * ^[ > 61 ; FIRMWARE ; KEYBOARD c + * whereas 65 is fixed for VT525 terminals, the last terminal-line that + * increased this number. FIRMWARE is the firmware + * version encoded as major/minor (20 == 2.0) and KEYBOARD is 0 for STD + * keyboard and 1 for PC keyboards. + * + * We replace the firmware-version with the systemd-version so clients + * can decode it again. + */ + + return SEQ_WRITE(screen, C0_CSI, C1_CSI, ">65;" PACKAGE_VERSION ";1c"); +} + +static int screen_DA3(term_screen *screen, const term_seq *seq) { + /* + * DA3 - tertiary-device-attributes + * The tertiary DA is used to query the terminal-ID. + * + * The terminal's response is: + * ^P ! | XX AA BB CC ^\ + * whereas all four parameters are hexadecimal-encoded pairs. XX + * denotes the manufacturing site, AA BB CC is the terminal's ID. + */ + + /* we do not support tertiary DAs */ + return 0; +} + +static int screen_DC1(term_screen *screen, const term_seq *seq) { + /* + * DC1 - device-control-1 or XON + * This clears any previous XOFF and resumes terminal-transmission. + */ + + /* we do not support XON */ + return 0; +} + +static int screen_DC3(term_screen *screen, const term_seq *seq) { + /* + * DC3 - device-control-3 or XOFF + * Stops terminal transmission. No further characters are sent until + * an XON is received. + */ + + /* we do not support XOFF */ + return 0; +} + +static int screen_DCH(term_screen *screen, const term_seq *seq) { + /* + * DCH - delete-character + * This deletes @argv[0] characters at the current cursor position. As + * characters are deleted, the remaining characters between the cursor + * and right margin move to the left. Character attributes move with the + * characters. The terminal adds blank spaces with no visual character + * attributes at the right margin. DCH has no effect outside the + * scrolling margins. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + term_page_delete_cells(screen->page, screen->cursor_x, screen->cursor_y, num, &screen->attr, screen->age); + + return 0; +} + +static int screen_DECALN(term_screen *screen, const term_seq *seq) { + /* + * DECALN - screen-alignment-pattern + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECANM(term_screen *screen, const term_seq *seq) { + /* + * DECANM - ansi-mode + * Set the terminal into VT52 compatibility mode. Control sequences + * overlap with regular sequences so we have to detect them early before + * dispatching them. + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECBI(term_screen *screen, const term_seq *seq) { + /* + * DECBI - back-index + * This control function moves the cursor backward one column. If the + * cursor is at the left margin, then all screen data within the margin + * moves one column to the right. The column that shifted past the right + * margin is lost. + * DECBI adds a new column at the left margin with no visual attributes. + * DECBI does not affect the margins. If the cursor is beyond the + * left-margin at the left border, then the terminal ignores DECBI. + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECCARA(term_screen *screen, const term_seq *seq) { + /* + * DECCARA - change-attributes-in-rectangular-area + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECCRA(term_screen *screen, const term_seq *seq) { + /* + * DECCRA - copy-rectangular-area + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECDC(term_screen *screen, const term_seq *seq) { + /* + * DECDC - delete-column + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECDHL_BH(term_screen *screen, const term_seq *seq) { + /* + * DECDHL_BH - double-width-double-height-line: bottom half + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECDHL_TH(term_screen *screen, const term_seq *seq) { + /* + * DECDHL_TH - double-width-double-height-line: top half + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECDWL(term_screen *screen, const term_seq *seq) { + /* + * DECDWL - double-width-single-height-line + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECEFR(term_screen *screen, const term_seq *seq) { + /* + * DECEFR - enable-filter-rectangle + * Defines the coordinates of a filter rectangle (top, left, bottom, + * right as @args[0] to @args[3]) and activates it. + * Anytime the locator is detected outside of the filter rectangle, an + * outside rectangle event is generated and the rectangle is disabled. + * Filter rectangles are always treated as "one-shot" events. Any + * parameters that are omitted default to the current locator position. + * If all parameters are omitted, any locator motion will be reported. + * DECELR always cancels any prevous rectangle definition. + * + * The locator is usually associated with the mouse-cursor, but based + * on cells instead of pixels. See DECELR how to initialize and enable + * it. DECELR can also enable pixel-mode instead of cell-mode. + * + * TODO: implement + */ + + return 0; +} + +static int screen_DECELF(term_screen *screen, const term_seq *seq) { + /* + * DECELF - enable-local-functions + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECELR(term_screen *screen, const term_seq *seq) { + /* + * DECELR - enable-locator-reporting + * This changes the locator-reporting mode. @args[0] specifies the mode + * to set, 0 disables locator-reporting, 1 enables it continously, 2 + * enables it for a single report. @args[1] specifies the + * precision-mode. 0 and 2 set the reporting to cell-precision, 1 sets + * pixel-precision. + * + * Defaults: + * args[0]: 0 + * args[1]: 0 + * + * TODO: implement + */ + + return 0; +} + +static int screen_DECERA(term_screen *screen, const term_seq *seq) { + /* + * DECERA - erase-rectangular-area + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECFI(term_screen *screen, const term_seq *seq) { + /* + * DECFI - forward-index + * This control function moves the cursor forward one column. If the + * cursor is at the right margin, then all screen data within the + * margins moves one column to the left. The column shifted past the + * left margin is lost. + * DECFI adds a new column at the right margin, with no visual + * attributes. DECFI does not affect margins. If the cursor is beyond + * the right margin at the border of the page when the terminal + * receives DECFI, then the terminal ignores DECFI. + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECFRA(term_screen *screen, const term_seq *seq) { + /* + * DECFRA - fill-rectangular-area + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECIC(term_screen *screen, const term_seq *seq) { + /* + * DECIC - insert-column + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECID(term_screen *screen, const term_seq *seq) { + /* + * DECID - return-terminal-id + * This is an obsolete form of TERM_CMD_DA1. + */ + + return screen_DA1(screen, seq); +} + +static int screen_DECINVM(term_screen *screen, const term_seq *seq) { + /* + * DECINVM - invoke-macro + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECKBD(term_screen *screen, const term_seq *seq) { + /* + * DECKBD - keyboard-language-selection + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECKPAM(term_screen *screen, const term_seq *seq) { + /* + * DECKPAM - keypad-application-mode + * Enables the keypad-application mode. If enabled, the keypad sends + * special characters instead of the printed characters. This way, + * applications can detect whether a numeric key was pressed on the + * top-row or on the keypad. + * Default is keypad-numeric-mode. + */ + + screen->flags |= TERM_FLAG_KEYPAD_MODE; + + return 0; +} + +static int screen_DECKPNM(term_screen *screen, const term_seq *seq) { + /* + * DECKPNM - keypad-numeric-mode + * This disables the keypad-application-mode (DECKPAM) and returns to + * the keypad-numeric-mode. Keypresses on the keypad generate the same + * sequences as corresponding keypresses on the main keyboard. + * Default is keypad-numeric-mode. + */ + + screen->flags &= ~TERM_FLAG_KEYPAD_MODE; + + return 0; +} + +static int screen_DECLFKC(term_screen *screen, const term_seq *seq) { + /* + * DECLFKC - local-function-key-control + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECLL(term_screen *screen, const term_seq *seq) { + /* + * DECLL - load-leds + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECLTOD(term_screen *screen, const term_seq *seq) { + /* + * DECLTOD - load-time-of-day + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECPCTERM(term_screen *screen, const term_seq *seq) { + /* + * DECPCTERM - pcterm-mode + * This enters/exits the PCTerm mode. Default mode is VT-mode. It can + * also select parameters for scancode/keycode mappings in SCO mode. + * + * Definitely not worth implementing. Lets kill PCTerm/SCO modes! + */ + + return 0; +} + +static int screen_DECPKA(term_screen *screen, const term_seq *seq) { + /* + * DECPKA - program-key-action + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECPKFMR(term_screen *screen, const term_seq *seq) { + /* + * DECPKFMR - program-key-free-memory-report + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECRARA(term_screen *screen, const term_seq *seq) { + /* + * DECRARA - reverse-attributes-in-rectangular-area + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECRC(term_screen *screen, const term_seq *seq) { + /* + * DECRC - restore-cursor + * Restores the terminal to the state saved by the save cursor (DECSC) + * function. This includes more than just the cursor-position. + * + * If nothing was saved by DECSC, then DECRC performs the following + * actions: + * * Moves the cursor to the home position (upper left of screen). + * * Resets origin mode (DECOM). + * * Turns all character attributes off (normal setting). + * * Maps the ASCII character set into GL, and the DEC Supplemental + * Graphic set into GR. + * + * The terminal maintains a separate DECSC buffer for the main display + * and the status line. This feature lets you save a separate operating + * state for the main display and the status line. + */ + + screen->attr = screen->saved.attr; + screen->gl = screen->saved.gl; + screen->gr = screen->saved.gr; + screen->glt = screen->saved.glt; + screen->grt = screen->saved.grt; + set_reset(screen, TERM_FLAG_AUTO_WRAP, screen->flags & TERM_FLAG_AUTO_WRAP); + set_reset(screen, TERM_FLAG_ORIGIN_MODE, screen->flags & TERM_FLAG_ORIGIN_MODE); + screen_cursor_set(screen, screen->saved.cursor_x, screen->saved.cursor_y); + + return 0; +} + +static int screen_DECREQTPARM(term_screen *screen, const term_seq *seq) { + /* + * DECREQTPARM - request-terminal-parameters + * The sequence DECREPTPARM is sent by the terminal controller to notify + * the host of the status of selected terminal parameters. The status + * sequence may be sent when requested by the host or at the terminal's + * discretion. DECREPTPARM is sent upon receipt of a DECREQTPARM. + * + * If @args[0] is 0, this marks a request and the terminal is allowed + * to send DECREPTPARM messages without request. If it is 1, the same + * applies but the terminal should no longer send DECREPTPARM + * unrequested. + * 2 and 3 mark a report, but 3 is only used if the terminal answers as + * an explicit request with @args[0] == 1. + * + * The other arguments are ignored in requests, but have the following + * meaning in responses: + * args[1]: 1=no-parity-set 4=parity-set-and-odd 5=parity-set-and-even + * args[2]: 1=8bits-per-char 2=7bits-per-char + * args[3]: transmission-speed + * args[4]: receive-speed + * args[5]: 1=bit-rate-multiplier-is-16 + * args[6]: This value communicates the four switch values in block 5 + * of SETUP B, which are only visible to the user when an STP + * option is installed. These bits may be assigned for an STP + * device. The four bits are a decimal-encoded binary number. + * Value between 0-15. + * + * The transmission/receive speeds have mappings for number => bits/s + * which are quite weird. Examples are: 96->3600, 112->9600, 120->19200 + * + * Defaults: + * args[0]: 0 + */ + + if (seq->n_args < 1 || seq->args[0] == 0) { + screen->flags &= ~TERM_FLAG_INHIBIT_TPARM; + return SEQ_WRITE(screen, C0_CSI, C1_CSI, "2;1;1;120;120;1;0x"); + } else if (seq->args[0] == 1) { + screen->flags |= TERM_FLAG_INHIBIT_TPARM; + return SEQ_WRITE(screen, C0_CSI, C1_CSI, "3;1;1;120;120;1;0x"); + } else { + return 0; + } +} + +static int screen_DECRPKT(term_screen *screen, const term_seq *seq) { + /* + * DECRPKT - report-key-type + * Response to DECRQKT, we can safely ignore it as we're the one sending + * it to the host. + */ + + return 0; +} + +static int screen_DECRQCRA(term_screen *screen, const term_seq *seq) { + /* + * DECRQCRA - request-checksum-of-rectangular-area + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECRQDE(term_screen *screen, const term_seq *seq) { + /* + * DECRQDE - request-display-extent + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECRQKT(term_screen *screen, const term_seq *seq) { + /* + * DECRQKT - request-key-type + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECRQLP(term_screen *screen, const term_seq *seq) { + /* + * DECRQLP - request-locator-position + * See DECELR for locator-information. + * + * TODO: document and implement + */ + + return 0; +} + +static int screen_DECRQM_ANSI(term_screen *screen, const term_seq *seq) { + /* + * DECRQM_ANSI - request-mode-ansi + * The host sends this control function to find out if a particular mode + * is set or reset. The terminal responds with a report mode function. + * @args[0] contains the mode to query. + * + * Response is DECRPM with the first argument set to the mode that was + * queried, second argument is 0 if mode is invalid, 1 if mode is set, + * 2 if mode is not set (reset), 3 if mode is permanently set and 4 if + * mode is permanently not set (reset): + * ANSI: ^[ MODE ; VALUE $ y + * DEC: ^[ ? MODE ; VALUE $ y + * + * TODO: implement + */ + + return 0; +} + +static int screen_DECRQM_DEC(term_screen *screen, const term_seq *seq) { + /* + * DECRQM_DEC - request-mode-dec + * Same as DECRQM_ANSI but for DEC modes. + * + * TODO: implement + */ + + return 0; +} + +static int screen_DECRQPKFM(term_screen *screen, const term_seq *seq) { + /* + * DECRQPKFM - request-program-key-free-memory + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECRQPSR(term_screen *screen, const term_seq *seq) { + /* + * DECRQPSR - request-presentation-state-report + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECRQTSR(term_screen *screen, const term_seq *seq) { + /* + * DECRQTSR - request-terminal-state-report + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECRQUPSS(term_screen *screen, const term_seq *seq) { + /* + * DECRQUPSS - request-user-preferred-supplemental-set + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSACE(term_screen *screen, const term_seq *seq) { + /* + * DECSACE - select-attribute-change-extent + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSASD(term_screen *screen, const term_seq *seq) { + /* + * DECSASD - select-active-status-display + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSC(term_screen *screen, const term_seq *seq) { + /* + * DECSC - save-cursor + * Save cursor and terminal state so it can be restored later on. + * Saves the following items in the terminal's memory: + * * Cursor position + * * Character attributes set by the SGR command + * * Character sets (G0, G1, G2, or G3) currently in GL and GR + * * Wrap flag (autowrap or no autowrap) + * * State of origin mode (DECOM) + * * Selective erase attribute + * * Any single shift 2 (SS2) or single shift 3 (SS3) functions sent + */ + + screen->saved.cursor_x = screen->cursor_x; + screen->saved.cursor_y = screen->cursor_y; + screen->saved.attr = screen->attr; + screen->saved.gl = screen->gl; + screen->saved.gr = screen->gr; + screen->saved.glt = screen->glt; + screen->saved.grt = screen->grt; + screen->saved.flags = screen->flags & (TERM_FLAG_AUTO_WRAP + | TERM_FLAG_ORIGIN_MODE); + + return 0; +} + +static int screen_DECSCA(term_screen *screen, const term_seq *seq) { + /* + * DECSCA - select-character-protection-attribute + * Defines the characters that come after it as erasable or not erasable + * from the screen. The selective erase control functions (DECSED and + * DECSEL) can only erase characters defined as erasable. + * + * @args[0] specifies the new mode. 0 and 2 mark any following character + * as erasable, 1 marks it as not erasable. + * + * Defaults: + * args[0]: 0 + */ + + unsigned int mode = 0; + + if (seq->args[0] > 0) + mode = seq->args[0]; + + switch (mode) { + case 0: + case 2: + screen->attr.protect = 0; + break; + case 1: + screen->attr.protect = 1; + break; + } + + return 0; +} + +static int screen_DECSCL(term_screen *screen, const term_seq *seq) { + /* + * DECSCL - select-conformance-level + * Select the terminal's operating level. The factory default is + * level 4 (VT Level 4 mode, 7-bit controls). + * When you change the conformance level, the terminal performs a hard + * reset (RIS). + * + * @args[0] defines the conformance-level, valid values are: + * 61: Level 1 (VT100) + * 62: Level 2 (VT200) + * 63: Level 3 (VT300) + * 64: Level 4 (VT400) + * @args[1] defines the 8bit-mode, valid values are: + * 0: 8-bit controls + * 1: 7-bit controls + * 2: 8-bit controls (same as 0) + * + * If @args[0] is 61, then @args[1] is ignored and 7bit controls are + * enforced. + * + * Defaults: + * args[0]: 64 + * args[1]: 0 + */ + + unsigned int level = 64, bit = 0; + + if (seq->n_args > 0) { + level = seq->args[0]; + if (seq->n_args > 1) + bit = seq->args[1]; + } + + term_screen_hard_reset(screen); + + switch (level) { + case 61: + screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT100; + screen->flags |= TERM_FLAG_7BIT_MODE; + break; + case 62 ... 69: + screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400; + if (bit == 1) + screen->flags |= TERM_FLAG_7BIT_MODE; + else + screen->flags &= ~TERM_FLAG_7BIT_MODE; + break; + } + + return 0; +} + +static int screen_DECSCP(term_screen *screen, const term_seq *seq) { + /* + * DECSCP - select-communication-port + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSCPP(term_screen *screen, const term_seq *seq) { + /* + * DECSCPP - select-columns-per-page + * Select columns per page. The number of rows is unaffected by this. + * @args[0] selectes the number of columns (width), DEC only defines 80 + * and 132, but we allow any integer here. 0 is equivalent to 80. + * Page content is *not* cleared and the cursor is left untouched. + * However, if the page is reduced in width and the cursor would be + * outside the visible region, it's set to the right border. Newly added + * cells are cleared. No data is retained outside the visible region. + * + * Defaults: + * args[0]: 0 + * + * TODO: implement + */ + + return 0; +} + +static int screen_DECSCS(term_screen *screen, const term_seq *seq) { + /* + * DECSCS - select-communication-speed + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSCUSR(term_screen *screen, const term_seq *seq) { + /* + * DECSCUSR - set-cursor-style + * This changes the style of the cursor. @args[0] can be one of: + * 0, 1: blinking block + * 2: steady block + * 3: blinking underline + * 4: steady underline + * Changing this setting does _not_ affect the cursor visibility itself. + * Use DECTCEM for that. + * + * Defaults: + * args[0]: 0 + * + * TODO: implement + */ + + return 0; +} + +static int screen_DECSDDT(term_screen *screen, const term_seq *seq) { + /* + * DECSDDT - select-disconnect-delay-time + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSDPT(term_screen *screen, const term_seq *seq) { + /* + * DECSDPT - select-digital-printed-data-type + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSED(term_screen *screen, const term_seq *seq) { + /* + * DECSED - selective-erase-in-display + * This control function erases some or all of the erasable characters + * in the display. DECSED can only erase characters defined as erasable + * by the DECSCA control function. DECSED works inside or outside the + * scrolling margins. + * + * @args[0] defines which regions are erased. If it is 0, all cells from + * the cursor (inclusive) till the end of the display are erase. If it + * is 1, all cells from the start of the display till the cursor + * (inclusive) are erased. If it is 2, all cells are erased. + * + * Defaults: + * args[0]: 0 + */ + + unsigned int mode = 0; + + if (seq->args[0] > 0) + mode = seq->args[0]; + + switch (mode) { + case 0: + term_page_erase(screen->page, + screen->cursor_x, screen->cursor_y, + screen->page->width, screen->page->height, + &screen->attr, screen->age, true); + break; + case 1: + term_page_erase(screen->page, + 0, 0, + screen->cursor_x, screen->cursor_y, + &screen->attr, screen->age, true); + break; + case 2: + term_page_erase(screen->page, + 0, 0, + screen->page->width, screen->page->height, + &screen->attr, screen->age, true); + break; + } + + return 0; +} + +static int screen_DECSEL(term_screen *screen, const term_seq *seq) { + /* + * DECSEL - selective-erase-in-line + * This control function erases some or all of the erasable characters + * in a single line of text. DECSEL erases only those characters defined + * as erasable by the DECSCA control function. DECSEL works inside or + * outside the scrolling margins. + * + * @args[0] defines the region to be erased. If it is 0, all cells from + * the cursor (inclusive) till the end of the line are erase. If it is + * 1, all cells from the start of the line till the cursor (inclusive) + * are erased. If it is 2, the whole line of the cursor is erased. + * + * Defaults: + * args[0]: 0 + */ + + unsigned int mode = 0; + + if (seq->args[0] > 0) + mode = seq->args[0]; + + switch (mode) { + case 0: + term_page_erase(screen->page, + screen->cursor_x, screen->cursor_y, + screen->page->width, screen->cursor_y, + &screen->attr, screen->age, true); + break; + case 1: + term_page_erase(screen->page, + 0, screen->cursor_y, + screen->cursor_x, screen->cursor_y, + &screen->attr, screen->age, true); + break; + case 2: + term_page_erase(screen->page, + 0, screen->cursor_y, + screen->page->width, screen->cursor_y, + &screen->attr, screen->age, true); + break; + } + + return 0; +} + +static int screen_DECSERA(term_screen *screen, const term_seq *seq) { + /* + * DECSERA - selective-erase-rectangular-area + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSFC(term_screen *screen, const term_seq *seq) { + /* + * DECSFC - select-flow-control + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSKCV(term_screen *screen, const term_seq *seq) { + /* + * DECSKCV - set-key-click-volume + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSLCK(term_screen *screen, const term_seq *seq) { + /* + * DECSLCK - set-lock-key-style + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSLE(term_screen *screen, const term_seq *seq) { + /* + * DECSLE - select-locator-events + * + * TODO: implement + */ + + return 0; +} + +static int screen_DECSLPP(term_screen *screen, const term_seq *seq) { + /* + * DECSLPP - set-lines-per-page + * Set the number of lines used for the page. @args[0] specifies the + * number of lines to be used. DEC only allows a limited number of + * choices, however, we allow all integers. 0 is equivalent to 24. + * + * Defaults: + * args[0]: 0 + * + * TODO: implement + */ + + return 0; +} + +static int screen_DECSLRM_OR_SC(term_screen *screen, const term_seq *seq) { + /* + * DECSLRM_OR_SC - set-left-and-right-margins or save-cursor + * + * TODO: Detect save-cursor and run it. DECSLRM is not worth + * implementing. + */ + + return 0; +} + +static int screen_DECSMBV(term_screen *screen, const term_seq *seq) { + /* + * DECSMBV - set-margin-bell-volume + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSMKR(term_screen *screen, const term_seq *seq) { + /* + * DECSMKR - select-modifier-key-reporting + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSNLS(term_screen *screen, const term_seq *seq) { + /* + * DECSNLS - set-lines-per-screen + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSPP(term_screen *screen, const term_seq *seq) { + /* + * DECSPP - set-port-parameter + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSPPCS(term_screen *screen, const term_seq *seq) { + /* + * DECSPPCS - select-pro-printer-character-set + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSPRTT(term_screen *screen, const term_seq *seq) { + /* + * DECSPRTT - select-printer-type + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSR(term_screen *screen, const term_seq *seq) { + /* + * DECSR - secure-reset + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSRFR(term_screen *screen, const term_seq *seq) { + /* + * DECSRFR - select-refresh-rate + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSSCLS(term_screen *screen, const term_seq *seq) { + /* + * DECSSCLS - set-scroll-speed + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSSDT(term_screen *screen, const term_seq *seq) { + /* + * DECSSDT - select-status-display-line-type + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSSL(term_screen *screen, const term_seq *seq) { + /* + * DECSSL - select-setup-language + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECST8C(term_screen *screen, const term_seq *seq) { + /* + * DECST8C - set-tab-at-every-8-columns + * Clear the tab-ruler and reset it to a tab at every 8th column, + * starting at 9 (though, setting a tab at 1 is fine as it has no + * effect). + */ + + unsigned int i; + + for (i = 0; i < screen->page->width; i += 8) + screen->tabs[i / 8] = 0x1; + + return 0; +} + +static int screen_DECSTBM(term_screen *screen, const term_seq *seq) { + /* + * DECSTBM - set-top-and-bottom-margins + * This control function sets the top and bottom margins for the current + * page. You cannot perform scrolling outside the margins. + * + * @args[0] defines the top margin, @args[1] defines the bottom margin. + * The bottom margin must be lower than the top-margin. + * + * This call resets the cursor position to 0/0 of the page. + * + * Defaults: + * args[0]: 1 + * args[1]: last page-line + */ + + unsigned int top, bottom; + + top = 1; + bottom = screen->page->height; + + if (seq->args[0] > 0) + top = seq->args[0]; + if (seq->args[1] > 0) + bottom = seq->args[1]; + + if (top > screen->page->height) + top = screen->page->height; + if (bottom > screen->page->height) + bottom = screen->page->height; + + if (top >= bottom || top > screen->page->height || bottom > screen->page->height) { + top = 1; + bottom = screen->page->height; + } + + term_page_set_scroll_region(screen->page_main, top - 1, bottom - top + 1); + term_page_set_scroll_region(screen->page_alt, top - 1, bottom - top + 1); + screen_cursor_clear_wrap(screen); + screen_cursor_set(screen, 0, 0); + + return 0; +} + +static int screen_DECSTR(term_screen *screen, const term_seq *seq) { + /* + * DECSTR - soft-terminal-reset + * Perform a soft reset to the default values. + */ + + term_screen_soft_reset(screen); + + return 0; +} + +static int screen_DECSTRL(term_screen *screen, const term_seq *seq) { + /* + * DECSTRL - set-transmit-rate-limit + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSWBV(term_screen *screen, const term_seq *seq) { + /* + * DECSWBV - set-warning-bell-volume + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECSWL(term_screen *screen, const term_seq *seq) { + /* + * DECSWL - single-width-single-height-line + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECTID(term_screen *screen, const term_seq *seq) { + /* + * DECTID - select-terminal-id + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECTME(term_screen *screen, const term_seq *seq) { + /* + * DECTME - terminal-mode-emulation + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DECTST(term_screen *screen, const term_seq *seq) { + /* + * DECTST - invoke-confidence-test + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_DL(term_screen *screen, const term_seq *seq) { + /* + * DL - delete-line + * This control function deletes one or more lines in the scrolling + * region, starting with the line that has the cursor. @args[0] defines + * the number of lines to delete. 0 is treated the same as 1. + * As lines are deleted, lines below the cursor and in the scrolling + * region move up. The terminal adds blank lines with no visual + * character attributes at the bottom of the scrolling region. If it is + * greater than the number of lines remaining on the page, DL deletes + * only the remaining lines. DL has no effect outside the scrolling + * margins. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + term_page_delete_lines(screen->page, screen->cursor_y, num, &screen->attr, screen->age); + + return 0; +} + +static int screen_DSR_ANSI(term_screen *screen, const term_seq *seq) { + /* + * DSR_ANSI - device-status-report-ansi + * + * TODO: implement + */ + + return 0; +} + +static int screen_DSR_DEC(term_screen *screen, const term_seq *seq) { + /* + * DSR_DEC - device-status-report-dec + * + * TODO: implement + */ + + return 0; +} + +static int screen_ECH(term_screen *screen, const term_seq *seq) { + /* + * ECH - erase-character + * This control function erases one or more characters, from the cursor + * position to the right. ECH clears character attributes from erased + * character positions. ECH works inside or outside the scrolling + * margins. + * @args[0] defines the number of characters to erase. 0 is treated the + * same as 1. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + term_page_erase(screen->page, + screen->cursor_x, screen->cursor_y, + screen->cursor_x + num, screen->cursor_y, + &screen->attr, screen->age, false); + + return 0; +} + +static int screen_ED(term_screen *screen, const term_seq *seq) { + /* + * ED - erase-in-display + * This control function erases characters from part or all of the + * display. When you erase complete lines, they become single-height, + * single-width lines, with all visual character attributes cleared. ED + * works inside or outside the scrolling margins. + * + * @args[0] defines the region to erase. 0 means from cursor (inclusive) + * till the end of the screen. 1 means from the start of the screen till + * the cursor (inclusive) and 2 means the whole screen. + * + * Defaults: + * args[0]: 0 + */ + + unsigned int mode = 0; + + if (seq->args[0] > 0) + mode = seq->args[0]; + + switch (mode) { + case 0: + term_page_erase(screen->page, + screen->cursor_x, screen->cursor_y, + screen->page->width, screen->page->height, + &screen->attr, screen->age, false); + break; + case 1: + term_page_erase(screen->page, + 0, 0, + screen->cursor_x, screen->cursor_y, + &screen->attr, screen->age, false); + break; + case 2: + term_page_erase(screen->page, + 0, 0, + screen->page->width, screen->page->height, + &screen->attr, screen->age, false); + break; + } + + return 0; +} + +static int screen_EL(term_screen *screen, const term_seq *seq) { + /* + * EL - erase-in-line + * This control function erases characters on the line that has the + * cursor. EL clears all character attributes from erased character + * positions. EL works inside or outside the scrolling margins. + * + * @args[0] defines the region to erase. 0 means from cursor (inclusive) + * till the end of the line. 1 means from the start of the line till the + * cursor (inclusive) and 2 means the whole line. + * + * Defaults: + * args[0]: 0 + */ + + unsigned int mode = 0; + + if (seq->args[0] > 0) + mode = seq->args[0]; + + switch (mode) { + case 0: + term_page_erase(screen->page, + screen->cursor_x, screen->cursor_y, + screen->page->width, screen->cursor_y, + &screen->attr, screen->age, false); + break; + case 1: + term_page_erase(screen->page, + 0, screen->cursor_y, + screen->cursor_x, screen->cursor_y, + &screen->attr, screen->age, false); + break; + case 2: + term_page_erase(screen->page, + 0, screen->cursor_y, + screen->page->width, screen->cursor_y, + &screen->attr, screen->age, false); + break; + } + + return 0; +} + +static int screen_ENQ(term_screen *screen, const term_seq *seq) { + /* + * ENQ - enquiry + * Transmit the answerback-string. If none is set, do nothing. + */ + + if (screen->answerback) + return screen_write(screen, screen->answerback, strlen(screen->answerback)); + + return 0; +} + +static int screen_EPA(term_screen *screen, const term_seq *seq) { + /* + * EPA - end-of-guarded-area + * + * TODO: What is this? + */ + + return 0; +} + +static int screen_FF(term_screen *screen, const term_seq *seq) { + /* + * FF - form-feed + * This causes the cursor to jump to the next line. It is treated the + * same as LF. + */ + + return screen_LF(screen, seq); +} + +static int screen_HPA(term_screen *screen, const term_seq *seq) { + /* + * HPA - horizontal-position-absolute + * HPA causes the active position to be moved to the n-th horizontal + * position of the active line. If an attempt is made to move the active + * position past the last position on the line, then the active position + * stops at the last position on the line. + * + * @args[0] defines the horizontal position. 0 is treated as 1. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_set(screen, num - 1, screen->cursor_y); + + return 0; +} + +static int screen_HPR(term_screen *screen, const term_seq *seq) { + /* + * HPR - horizontal-position-relative + * HPR causes the active position to be moved to the n-th following + * horizontal position of the active line. If an attempt is made to move + * the active position past the last position on the line, then the + * active position stops at the last position on the line. + * + * @args[0] defines the horizontal position. 0 is treated as 1. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_right(screen, num); + + return 0; +} + +static int screen_HT(term_screen *screen, const term_seq *seq) { + /* + * HT - horizontal-tab + * Moves the cursor to the next tab stop. If there are no more tab + * stops, the cursor moves to the right margin. HT does not cause text + * to auto wrap. + */ + + screen_cursor_clear_wrap(screen); + screen_cursor_right_tab(screen, 1); + + return 0; +} + +static int screen_HTS(term_screen *screen, const term_seq *seq) { + /* + * HTS - horizontal-tab-set + * HTS sets a horizontal tab stop at the column position indicated by + * the value of the active column when the terminal receives an HTS. + * + * Executing an HTS does not effect the other horizontal tab stop + * settings. + */ + + unsigned int pos; + + pos = screen->cursor_x; + if (screen->page->width > 0) + screen->tabs[pos / 8] |= 1U << (pos % 8); + + return 0; +} + +static int screen_HVP(term_screen *screen, const term_seq *seq) { + /* + * HVP - horizontal-and-vertical-position + * This control function works the same as the cursor position (CUP) + * function. Origin mode (DECOM) selects line numbering and the ability + * to move the cursor into margins. + * + * Defaults: + * args[0]: 1 + * args[1]: 1 + */ + + return screen_CUP(screen, seq); +} + +static int screen_ICH(term_screen *screen, const term_seq *seq) { + /* + * ICH - insert-character + * This control function inserts one or more space (SP) characters + * starting at the cursor position. @args[0] is the number of characters + * to insert. 0 is treated as 1. + * + * The ICH sequence inserts blank characters with the normal + * character attribute. The cursor remains at the beginning of the blank + * characters. Text between the cursor and right margin moves to the + * right. Characters scrolled past the right margin are lost. ICH has no + * effect outside the scrolling margins. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + term_page_insert_cells(screen->page, screen->cursor_x, screen->cursor_y, num, &screen->attr, screen->age); + + return 0; +} + +static int screen_IL(term_screen *screen, const term_seq *seq) { + /* + * IL - insert-line + * This control function inserts one or more blank lines, starting at + * the cursor. @args[0] is the number of lines to insert. 0 is treated + * as 1. + * + * As lines are inserted, lines below the cursor and in the scrolling + * region move down. Lines scrolled off the page are lost. IL has no + * effect outside the page margins. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + term_page_insert_lines(screen->page, screen->cursor_y, num, &screen->attr, screen->age); + + return 0; +} + +static int screen_IND(term_screen *screen, const term_seq *seq) { + /* + * IND - index + * IND moves the cursor down one line in the same column. If the cursor + * is at the bottom margin, then the screen performs a scroll-up. + */ + + screen_cursor_down(screen, 1, true); + + return 0; +} + +static int screen_LF(term_screen *screen, const term_seq *seq) { + /* + * LF - line-feed + * Causes a line feed or a new line operation, depending on the setting + * of line feed/new line mode. + */ + + screen_cursor_down(screen, 1, true); + if (screen->flags & TERM_FLAG_NEWLINE_MODE) + screen_cursor_left(screen, screen->cursor_x); + + return 0; +} + +static int screen_LS1R(term_screen *screen, const term_seq *seq) { + /* + * LS1R - locking-shift-1-right + * Map G1 into GR. + */ + + screen->gr = &screen->g1; + + return 0; +} + +static int screen_LS2(term_screen *screen, const term_seq *seq) { + /* + * LS2 - locking-shift-2 + * Map G2 into GL. + */ + + screen->gl = &screen->g2; + + return 0; +} + +static int screen_LS2R(term_screen *screen, const term_seq *seq) { + /* + * LS2R - locking-shift-2-right + * Map G2 into GR. + */ + + screen->gr = &screen->g2; + + return 0; +} + +static int screen_LS3(term_screen *screen, const term_seq *seq) { + /* + * LS3 - locking-shift-3 + * Map G3 into GL. + */ + + screen->gl = &screen->g3; + + return 0; +} + +static int screen_LS3R(term_screen *screen, const term_seq *seq) { + /* + * LS3R - locking-shift-3-right + * Map G3 into GR. + */ + + screen->gr = &screen->g3; + + return 0; +} + +static int screen_MC_ANSI(term_screen *screen, const term_seq *seq) { + /* + * MC_ANSI - media-copy-ansi + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_MC_DEC(term_screen *screen, const term_seq *seq) { + /* + * MC_DEC - media-copy-dec + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_NEL(term_screen *screen, const term_seq *seq) { + /* + * NEL - next-line + * Moves cursor to first position on next line. If cursor is at bottom + * margin, then screen performs a scroll-up. + */ + + screen_cursor_clear_wrap(screen); + screen_cursor_down(screen, 1, true); + screen_cursor_set(screen, 0, screen->cursor_y); + + return 0; +} + +static int screen_NP(term_screen *screen, const term_seq *seq) { + /* + * NP - next-page + * This control function moves the cursor forward to the home position + * on one of the following pages in page memory. If there is only one + * page, then the terminal ignores NP. + * If NP tries to move the cursor past the last page in memory, then the + * cursor stops at the last page. + * + * @args[0] defines the number of pages to forward. 0 is treated as 1. + * + * Defaults: + * args[0]: 1 + * + * Probably not worth implementing. We only support a single page. + */ + + return 0; +} + +static int screen_NULL(term_screen *screen, const term_seq *seq) { + /* + * NULL - null + * The NULL operation does nothing. ASCII NULL is always ignored. + */ + + return 0; +} + +static int screen_PP(term_screen *screen, const term_seq *seq) { + /* + * PP - preceding-page + * This control function moves the cursor backward to the home position + * on one of the preceding pages in page memory. If there is only one + * page, then the terminal ignores PP. + * If PP tries to move the cursor back farther than the first page in + * memory, then the cursor stops at the first page. + * + * @args[0] defines the number of pages to go backwards. 0 is treated + * as 1. + * + * Defaults: + * args[0]: 1 + * + * Probably not worth implementing. We only support a single page. + */ + + return 0; +} + +static int screen_PPA(term_screen *screen, const term_seq *seq) { + /* + * PPA - page-position-absolute + * This control function can move the cursor to the corresponding row + * and column on any page in page memory. You select the page by its + * number. If there is only one page, then the terminal ignores PPA. + * + * @args[0] is the number of the page to move the cursor to. If it is + * greater than the number of the last page in memory, then the cursor + * stops at the last page. If it is less than the number of the first + * page, then the cursor stops at the first page. + * + * Defaults: + * args[0]: 1 + * + * Probably not worth implementing. We only support a single page. + */ + + return 0; +} + +static int screen_PPB(term_screen *screen, const term_seq *seq) { + /* + * PPB - page-position-backward + * This control function moves the cursor backward to the corresponding + * row and column on one of the preceding pages in page memory. If there + * is only one page, then the terminal ignores PPB. + * + * @args[0] indicates the number of pages to move the cursor backward. + * If it tries to move the cursor back farther than the first page in + * memory, then the cursor stops at the first page. 0 is treated as 1. + * + * Defaults: + * args[0]: 1 + * + * Probably not worth implementing. We only support a single page. + */ + + return 0; +} + +static int screen_PPR(term_screen *screen, const term_seq *seq) { + /* + * PPR - page-position-relative + * This control function moves the cursor forward to the corresponding + * row and column on one of the following pages in page memory. If there + * is only one page, then the terminal ignores PPR. + * + * @args[0] indicates how many pages to move the cursor forward. If it + * tries to move the cursor beyond the last page in memory, then the + * cursor stops at the last page. 0 is treated as 1. + * + * Defaults: + * args[0]: 1 + * + * Probably not worth implementing. We only support a single page. + */ + + return 0; +} + +static int screen_RC(term_screen *screen, const term_seq *seq) { + /* + * RC - restore-cursor + */ + + return screen_DECRC(screen, seq); +} + +static int screen_REP(term_screen *screen, const term_seq *seq) { + /* + * REP - repeat + * Repeat the preceding graphics-character the given number of times. + * @args[0] specifies how often it shall be repeated. 0 is treated as 1. + * + * Defaults: + * args[0]: 1 + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_RI(term_screen *screen, const term_seq *seq) { + /* + * RI - reverse-index + * Moves the cursor up one line in the same column. If the cursor is at + * the top margin, the page scrolls down. + */ + + screen_cursor_up(screen, 1, true); + + return 0; +} + +static int screen_RIS(term_screen *screen, const term_seq *seq) { + /* + * RIS - reset-to-initial-state + * This control function causes a nonvolatile memory (NVR) recall to + * occur. RIS replaces all set-up features with their saved settings. + * + * The terminal stores these saved settings in NVR memory. The saved + * setting for a feature is the same as the factory-default setting, + * unless you saved a new setting. + */ + + term_screen_hard_reset(screen); + + return 0; +} + +static int screen_RM_ANSI(term_screen *screen, const term_seq *seq) { + /* + * RM_ANSI - reset-mode-ansi + * + * TODO: implement (see VT510rm manual) + */ + + unsigned int i; + + for (i = 0; i < seq->n_args; ++i) + screen_mode_change(screen, seq->args[i], false, false); + + return 0; +} + +static int screen_RM_DEC(term_screen *screen, const term_seq *seq) { + /* + * RM_DEC - reset-mode-dec + * This is the same as RM_ANSI but for DEC modes. + */ + + unsigned int i; + + for (i = 0; i < seq->n_args; ++i) + screen_mode_change(screen, seq->args[i], true, false); + + return 0; +} + +static int screen_S7C1T(term_screen *screen, const term_seq *seq) { + /* + * S7C1T - set-7bit-c1-terminal + * This causes the terminal to start sending C1 controls as 7bit + * sequences instead of 8bit C1 controls. + * This is ignored if the terminal is below level-2 emulation mode + * (VT100 and below), the terminal already sends 7bit controls then. + */ + + if (screen->conformance_level > TERM_CONFORMANCE_LEVEL_VT100) + screen->flags |= TERM_FLAG_7BIT_MODE; + + return 0; +} + +static int screen_S8C1T(term_screen *screen, const term_seq *seq) { + /* + * S8C1T - set-8bit-c1-terminal + * This causes the terminal to start sending C1 controls as 8bit C1 + * control instead of 7bit sequences. + * This is ignored if the terminal is below level-2 emulation mode + * (VT100 and below). The terminal always sends 7bit controls in those + * modes. + */ + + if (screen->conformance_level > TERM_CONFORMANCE_LEVEL_VT100) + screen->flags &= ~TERM_FLAG_7BIT_MODE; + + return 0; +} + +static int screen_SCS(term_screen *screen, const term_seq *seq) { + /* + * SCS - select-character-set + * Designate character sets to G-sets. The mapping from intermediates + * and terminal characters in the escape sequence to G-sets and + * character-sets is non-trivial and implemented separately. See there + * for more information. + * This call simply sets the selected G-set to the desired + * character-set. + */ + + term_charset *cs = NULL; + + /* TODO: support more of them? */ + switch (seq->charset) { + case TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL: + case TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL: + case TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL: + case TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL: + case TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL: + case TERM_CHARSET_ISO_LATIN_CYRILLIC: + break; + + case TERM_CHARSET_DEC_SPECIAL_GRAPHIC: + cs = &term_dec_special_graphics; + break; + case TERM_CHARSET_DEC_SUPPLEMENTAL: + cs = &term_dec_supplemental_graphics; + break; + case TERM_CHARSET_DEC_TECHNICAL: + case TERM_CHARSET_CYRILLIC_DEC: + case TERM_CHARSET_DUTCH_NRCS: + case TERM_CHARSET_FINNISH_NRCS: + case TERM_CHARSET_FRENCH_NRCS: + case TERM_CHARSET_FRENCH_CANADIAN_NRCS: + case TERM_CHARSET_GERMAN_NRCS: + case TERM_CHARSET_GREEK_DEC: + case TERM_CHARSET_GREEK_NRCS: + case TERM_CHARSET_HEBREW_DEC: + case TERM_CHARSET_HEBREW_NRCS: + case TERM_CHARSET_ITALIAN_NRCS: + case TERM_CHARSET_NORWEGIAN_DANISH_NRCS: + case TERM_CHARSET_PORTUGUESE_NRCS: + case TERM_CHARSET_RUSSIAN_NRCS: + case TERM_CHARSET_SCS_NRCS: + case TERM_CHARSET_SPANISH_NRCS: + case TERM_CHARSET_SWEDISH_NRCS: + case TERM_CHARSET_SWISS_NRCS: + case TERM_CHARSET_TURKISH_DEC: + case TERM_CHARSET_TURKISH_NRCS: + break; + + case TERM_CHARSET_USERPREF_SUPPLEMENTAL: + break; + } + + if (seq->intermediates & TERM_SEQ_FLAG_POPEN) + screen->g0 = cs ? : &term_unicode_lower; + else if (seq->intermediates & TERM_SEQ_FLAG_PCLOSE) + screen->g1 = cs ? : &term_unicode_upper; + else if (seq->intermediates & TERM_SEQ_FLAG_MULT) + screen->g2 = cs ? : &term_unicode_lower; + else if (seq->intermediates & TERM_SEQ_FLAG_PLUS) + screen->g3 = cs ? : &term_unicode_upper; + else if (seq->intermediates & TERM_SEQ_FLAG_MINUS) + screen->g1 = cs ? : &term_unicode_upper; + else if (seq->intermediates & TERM_SEQ_FLAG_DOT) + screen->g2 = cs ? : &term_unicode_lower; + else if (seq->intermediates & TERM_SEQ_FLAG_SLASH) + screen->g3 = cs ? : &term_unicode_upper; + + return 0; +} + +static int screen_SD(term_screen *screen, const term_seq *seq) { + /* + * SD - scroll-down + * This control function moves the user window down a specified number + * of lines in page memory. + * @args[0] is the number of lines to move the + * user window up in page memory. New lines appear at the top of the + * display. Old lines disappear at the bottom of the display. You + * cannot pan past the top margin of the current page. 0 is treated + * as 1. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + term_page_scroll_down(screen->page, num, &screen->attr, screen->age, NULL); + + return 0; +} + +static int screen_SGR(term_screen *screen, const term_seq *seq) { + /* + * SGR - select-graphics-rendition + */ + + term_color *dst; + unsigned int i, code; + int v; + + if (seq->n_args < 1) { + zero(screen->attr); + return 0; + } + + for (i = 0; i < seq->n_args; ++i) { + v = seq->args[i]; + switch (v) { + case 1: + screen->attr.bold = 1; + break; + case 3: + screen->attr.italic = 1; + break; + case 4: + screen->attr.underline = 1; + break; + case 5: + screen->attr.blink = 1; + break; + case 7: + screen->attr.inverse = 1; + break; + case 8: + screen->attr.hidden = 1; + break; + case 22: + screen->attr.bold = 0; + break; + case 23: + screen->attr.italic = 0; + break; + case 24: + screen->attr.underline = 0; + break; + case 25: + screen->attr.blink = 0; + break; + case 27: + screen->attr.inverse = 0; + break; + case 28: + screen->attr.hidden = 0; + break; + case 30 ... 37: + screen->attr.fg.ccode = v - 30 + TERM_CCODE_BLACK; + break; + case 39: + screen->attr.fg.ccode = 0; + break; + case 40 ... 47: + screen->attr.bg.ccode = v - 40 + TERM_CCODE_BLACK; + break; + case 49: + screen->attr.bg.ccode = 0; + break; + case 90 ... 97: + screen->attr.fg.ccode = v - 90 + TERM_CCODE_LIGHT_BLACK; + break; + case 100 ... 107: + screen->attr.bg.ccode = v - 100 + TERM_CCODE_LIGHT_BLACK; + break; + case 38: + /* fallthrough */ + case 48: + + if (v == 38) + dst = &screen->attr.fg; + else + dst = &screen->attr.bg; + + ++i; + if (i >= seq->n_args) + break; + + switch (seq->args[i]) { + case 2: + /* 24bit-color support */ + + i += 3; + if (i >= seq->n_args) + break; + + dst->ccode = TERM_CCODE_RGB; + dst->red = (seq->args[i - 2] >= 0) ? seq->args[i - 2] : 0; + dst->green = (seq->args[i - 1] >= 0) ? seq->args[i - 1] : 0; + dst->blue = (seq->args[i] >= 0) ? seq->args[i] : 0; + + break; + case 5: + /* 256-color support */ + + ++i; + if (i >= seq->n_args || seq->args[i] < 0) + break; + + code = seq->args[i]; + if (code < 16) { + dst->ccode = code; + } else if (code < 232) { + static const uint8_t bval[] = { + 0x00, 0x5f, 0x87, + 0xaf, 0xd7, 0xff, + }; + + dst->ccode = TERM_CCODE_256; + dst->c256 = code; + code -= 16; + dst->blue = bval[code % 6]; + code /= 6; + dst->green = bval[code % 6]; + code /= 6; + dst->red = bval[code % 6]; + } else if (code < 256) { + dst->ccode = TERM_CCODE_256; + dst->c256 = code; + code = (code - 232) * 10 + 8; + dst->red = code; + dst->green = code; + dst->blue = code; + } + + break; + } + + break; + case -1: + /* fallthrough */ + case 0: + zero(screen->attr); + break; + } + } + + return 0; +} + +static int screen_SI(term_screen *screen, const term_seq *seq) { + /* + * SI - shift-in + * Map G0 into GL. + */ + + screen->gl = &screen->g0; + + return 0; +} + +static int screen_SM_ANSI(term_screen *screen, const term_seq *seq) { + /* + * SM_ANSI - set-mode-ansi + * + * TODO: implement + */ + + unsigned int i; + + for (i = 0; i < seq->n_args; ++i) + screen_mode_change(screen, seq->args[i], false, true); + + return 0; +} + +static int screen_SM_DEC(term_screen *screen, const term_seq *seq) { + /* + * SM_DEC - set-mode-dec + * This is the same as SM_ANSI but for DEC modes. + */ + + unsigned int i; + + for (i = 0; i < seq->n_args; ++i) + screen_mode_change(screen, seq->args[i], true, true); + + return 0; +} + +static int screen_SO(term_screen *screen, const term_seq *seq) { + /* + * SO - shift-out + * Map G1 into GL. + */ + + screen->gl = &screen->g1; + + return 0; +} + +static int screen_SPA(term_screen *screen, const term_seq *seq) { + /* + * SPA - start-of-protected-area + * + * TODO: What is this? + */ + + return 0; +} + +static int screen_SS2(term_screen *screen, const term_seq *seq) { + /* + * SS2 - single-shift-2 + * Temporarily map G2 into GL for the next graphics character. + */ + + screen->glt = &screen->g2; + + return 0; +} + +static int screen_SS3(term_screen *screen, const term_seq *seq) { + /* + * SS3 - single-shift-3 + * Temporarily map G3 into GL for the next graphics character + */ + + screen->glt = &screen->g3; + + return 0; +} + +static int screen_ST(term_screen *screen, const term_seq *seq) { + /* + * ST - string-terminator + * The string-terminator is usually part of control-sequences and + * handled by the parser. In all other situations it is silently + * ignored. + */ + + return 0; +} + +static int screen_SU(term_screen *screen, const term_seq *seq) { + /* + * SU - scroll-up + * This control function moves the user window up a specified number of + * lines in page memory. + * @args[0] is the number of lines to move the + * user window down in page memory. New lines appear at the bottom of + * the display. Old lines disappear at the top of the display. You + * cannot pan past the bottom margin of the current page. 0 is treated + * as 1. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + term_page_scroll_up(screen->page, num, &screen->attr, screen->age, screen->history); + + return 0; +} + +static int screen_SUB(term_screen *screen, const term_seq *seq) { + /* + * SUB - substitute + * Cancel the current control-sequence and print a replacement + * character. Our parser already handles this so all we have to do is + * print the replacement character. + */ + + static const term_seq rep = { + .type = TERM_SEQ_GRAPHIC, + .command = TERM_CMD_GRAPHIC, + .terminator = 0xfffd, + }; + + return screen_GRAPHIC(screen, &rep); +} + +static int screen_TBC(term_screen *screen, const term_seq *seq) { + /* + * TBC - tab-clear + * This clears tab-stops. If @args[0] is 0, the tab-stop at the current + * cursor position is cleared. If it is 3, all tab stops are cleared. + * + * Defaults: + * args[0]: 0 + */ + + unsigned int mode = 0, pos; + + if (seq->args[0] > 0) + mode = seq->args[0]; + + switch (mode) { + case 0: + pos = screen->cursor_x; + if (screen->page->width > 0) + screen->tabs[pos / 8] &= ~(1U << (pos % 8)); + break; + case 3: + if (screen->page->width > 0) + memset(screen->tabs, 0, (screen->page->width + 7) / 8); + break; + } + + return 0; +} + +static int screen_VPA(term_screen *screen, const term_seq *seq) { + /* + * VPA - vertical-line-position-absolute + * VPA causes the active position to be moved to the corresponding + * horizontal position. @args[0] specifies the line to jump to. If an + * attempt is made to move the active position below the last line, then + * the active position stops on the last line. 0 is treated as 1. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int pos = 1; + + if (seq->args[0] > 0) + pos = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_set_rel(screen, screen->cursor_x, pos - 1); + + return 0; +} + +static int screen_VPR(term_screen *screen, const term_seq *seq) { + /* + * VPR - vertical-line-position-relative + * VPR causes the active position to be moved to the corresponding + * horizontal position. @args[0] specifies the number of lines to jump + * down relative to the current cursor position. If an attempt is made + * to move the active position below the last line, the active position + * stops at the last line. 0 is treated as 1. + * + * Defaults: + * args[0]: 1 + */ + + unsigned int num = 1; + + if (seq->args[0] > 0) + num = seq->args[0]; + + screen_cursor_clear_wrap(screen); + screen_cursor_down(screen, num, false); + + return 0; +} + +static int screen_VT(term_screen *screen, const term_seq *seq) { + /* + * VT - vertical-tab + * This causes a vertical jump by one line. Terminals treat it exactly + * the same as LF. + */ + + return screen_LF(screen, seq); +} + +static int screen_XTERM_CLLHP(term_screen *screen, const term_seq *seq) { + /* + * XTERM_CLLHP - xterm-cursor-lower-left-hp-bugfix + * Move the cursor to the lower-left corner of the page. This is an HP + * bugfix by xterm. + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_IHMT(term_screen *screen, const term_seq *seq) { + /* + * XTERM_IHMT - xterm-initiate-highlight-mouse-tracking + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_MLHP(term_screen *screen, const term_seq *seq) { + /* + * XTERM_MLHP - xterm-memory-lock-hp-bugfix + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_MUHP(term_screen *screen, const term_seq *seq) { + /* + * XTERM_MUHP - xterm-memory-unlock-hp-bugfix + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_RPM(term_screen *screen, const term_seq *seq) { + /* + * XTERM_RPM - xterm-restore-private-mode + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_RRV(term_screen *screen, const term_seq *seq) { + /* + * XTERM_RRV - xterm-reset-resource-value + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_RTM(term_screen *screen, const term_seq *seq) { + /* + * XTERM_RTM - xterm-reset-title-mode + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_SACL1(term_screen *screen, const term_seq *seq) { + /* + * XTERM_SACL1 - xterm-set-ansi-conformance-level-1 + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_SACL2(term_screen *screen, const term_seq *seq) { + /* + * XTERM_SACL2 - xterm-set-ansi-conformance-level-2 + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_SACL3(term_screen *screen, const term_seq *seq) { + /* + * XTERM_SACL3 - xterm-set-ansi-conformance-level-3 + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_SDCS(term_screen *screen, const term_seq *seq) { + /* + * XTERM_SDCS - xterm-set-default-character-set + * Select the default character set. We treat this the same as UTF-8 as + * this is our default character set. As we always use UTF-8, this + * becomes as no-op. + */ + + return 0; +} + +static int screen_XTERM_SGFX(term_screen *screen, const term_seq *seq) { + /* + * XTERM_SGFX - xterm-sixel-graphics + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_SPM(term_screen *screen, const term_seq *seq) { + /* + * XTERM_SPM - xterm-set-private-mode + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_SRV(term_screen *screen, const term_seq *seq) { + /* + * XTERM_SRV - xterm-set-resource-value + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_STM(term_screen *screen, const term_seq *seq) { + /* + * XTERM_STM - xterm-set-title-mode + * + * Probably not worth implementing. + */ + + return 0; +} + +static int screen_XTERM_SUCS(term_screen *screen, const term_seq *seq) { + /* + * XTERM_SUCS - xterm-select-utf8-character-set + * Select UTF-8 as character set. This is our default on only character + * set. Hence, this is a no-op. + */ + + return 0; +} + +static int screen_XTERM_WM(term_screen *screen, const term_seq *seq) { + /* + * XTERM_WM - xterm-window-management + * + * Probably not worth implementing. + */ + + return 0; +} + +/* + * Feeding data + * The screen_feed_*() handlers take data from the user and feed it into the + * screen. Once the parser has detected a sequence, we parse the command-type + * and forward it to the command-dispatchers. + */ + +static int screen_feed_cmd(term_screen *screen, const term_seq *seq) { + switch (seq->command) { + case TERM_CMD_GRAPHIC: + return screen_GRAPHIC(screen, seq); + case TERM_CMD_BEL: + return screen_BEL(screen, seq); + case TERM_CMD_BS: + return screen_BS(screen, seq); + case TERM_CMD_CBT: + return screen_CBT(screen, seq); + case TERM_CMD_CHA: + return screen_CHA(screen, seq); + case TERM_CMD_CHT: + return screen_CHT(screen, seq); + case TERM_CMD_CNL: + return screen_CNL(screen, seq); + case TERM_CMD_CPL: + return screen_CPL(screen, seq); + case TERM_CMD_CR: + return screen_CR(screen, seq); + case TERM_CMD_CUB: + return screen_CUB(screen, seq); + case TERM_CMD_CUD: + return screen_CUD(screen, seq); + case TERM_CMD_CUF: + return screen_CUF(screen, seq); + case TERM_CMD_CUP: + return screen_CUP(screen, seq); + case TERM_CMD_CUU: + return screen_CUU(screen, seq); + case TERM_CMD_DA1: + return screen_DA1(screen, seq); + case TERM_CMD_DA2: + return screen_DA2(screen, seq); + case TERM_CMD_DA3: + return screen_DA3(screen, seq); + case TERM_CMD_DC1: + return screen_DC1(screen, seq); + case TERM_CMD_DC3: + return screen_DC3(screen, seq); + case TERM_CMD_DCH: + return screen_DCH(screen, seq); + case TERM_CMD_DECALN: + return screen_DECALN(screen, seq); + case TERM_CMD_DECANM: + return screen_DECANM(screen, seq); + case TERM_CMD_DECBI: + return screen_DECBI(screen, seq); + case TERM_CMD_DECCARA: + return screen_DECCARA(screen, seq); + case TERM_CMD_DECCRA: + return screen_DECCRA(screen, seq); + case TERM_CMD_DECDC: + return screen_DECDC(screen, seq); + case TERM_CMD_DECDHL_BH: + return screen_DECDHL_BH(screen, seq); + case TERM_CMD_DECDHL_TH: + return screen_DECDHL_TH(screen, seq); + case TERM_CMD_DECDWL: + return screen_DECDWL(screen, seq); + case TERM_CMD_DECEFR: + return screen_DECEFR(screen, seq); + case TERM_CMD_DECELF: + return screen_DECELF(screen, seq); + case TERM_CMD_DECELR: + return screen_DECELR(screen, seq); + case TERM_CMD_DECERA: + return screen_DECERA(screen, seq); + case TERM_CMD_DECFI: + return screen_DECFI(screen, seq); + case TERM_CMD_DECFRA: + return screen_DECFRA(screen, seq); + case TERM_CMD_DECIC: + return screen_DECIC(screen, seq); + case TERM_CMD_DECID: + return screen_DECID(screen, seq); + case TERM_CMD_DECINVM: + return screen_DECINVM(screen, seq); + case TERM_CMD_DECKBD: + return screen_DECKBD(screen, seq); + case TERM_CMD_DECKPAM: + return screen_DECKPAM(screen, seq); + case TERM_CMD_DECKPNM: + return screen_DECKPNM(screen, seq); + case TERM_CMD_DECLFKC: + return screen_DECLFKC(screen, seq); + case TERM_CMD_DECLL: + return screen_DECLL(screen, seq); + case TERM_CMD_DECLTOD: + return screen_DECLTOD(screen, seq); + case TERM_CMD_DECPCTERM: + return screen_DECPCTERM(screen, seq); + case TERM_CMD_DECPKA: + return screen_DECPKA(screen, seq); + case TERM_CMD_DECPKFMR: + return screen_DECPKFMR(screen, seq); + case TERM_CMD_DECRARA: + return screen_DECRARA(screen, seq); + case TERM_CMD_DECRC: + return screen_DECRC(screen, seq); + case TERM_CMD_DECREQTPARM: + return screen_DECREQTPARM(screen, seq); + case TERM_CMD_DECRPKT: + return screen_DECRPKT(screen, seq); + case TERM_CMD_DECRQCRA: + return screen_DECRQCRA(screen, seq); + case TERM_CMD_DECRQDE: + return screen_DECRQDE(screen, seq); + case TERM_CMD_DECRQKT: + return screen_DECRQKT(screen, seq); + case TERM_CMD_DECRQLP: + return screen_DECRQLP(screen, seq); + case TERM_CMD_DECRQM_ANSI: + return screen_DECRQM_ANSI(screen, seq); + case TERM_CMD_DECRQM_DEC: + return screen_DECRQM_DEC(screen, seq); + case TERM_CMD_DECRQPKFM: + return screen_DECRQPKFM(screen, seq); + case TERM_CMD_DECRQPSR: + return screen_DECRQPSR(screen, seq); + case TERM_CMD_DECRQTSR: + return screen_DECRQTSR(screen, seq); + case TERM_CMD_DECRQUPSS: + return screen_DECRQUPSS(screen, seq); + case TERM_CMD_DECSACE: + return screen_DECSACE(screen, seq); + case TERM_CMD_DECSASD: + return screen_DECSASD(screen, seq); + case TERM_CMD_DECSC: + return screen_DECSC(screen, seq); + case TERM_CMD_DECSCA: + return screen_DECSCA(screen, seq); + case TERM_CMD_DECSCL: + return screen_DECSCL(screen, seq); + case TERM_CMD_DECSCP: + return screen_DECSCP(screen, seq); + case TERM_CMD_DECSCPP: + return screen_DECSCPP(screen, seq); + case TERM_CMD_DECSCS: + return screen_DECSCS(screen, seq); + case TERM_CMD_DECSCUSR: + return screen_DECSCUSR(screen, seq); + case TERM_CMD_DECSDDT: + return screen_DECSDDT(screen, seq); + case TERM_CMD_DECSDPT: + return screen_DECSDPT(screen, seq); + case TERM_CMD_DECSED: + return screen_DECSED(screen, seq); + case TERM_CMD_DECSEL: + return screen_DECSEL(screen, seq); + case TERM_CMD_DECSERA: + return screen_DECSERA(screen, seq); + case TERM_CMD_DECSFC: + return screen_DECSFC(screen, seq); + case TERM_CMD_DECSKCV: + return screen_DECSKCV(screen, seq); + case TERM_CMD_DECSLCK: + return screen_DECSLCK(screen, seq); + case TERM_CMD_DECSLE: + return screen_DECSLE(screen, seq); + case TERM_CMD_DECSLPP: + return screen_DECSLPP(screen, seq); + case TERM_CMD_DECSLRM_OR_SC: + return screen_DECSLRM_OR_SC(screen, seq); + case TERM_CMD_DECSMBV: + return screen_DECSMBV(screen, seq); + case TERM_CMD_DECSMKR: + return screen_DECSMKR(screen, seq); + case TERM_CMD_DECSNLS: + return screen_DECSNLS(screen, seq); + case TERM_CMD_DECSPP: + return screen_DECSPP(screen, seq); + case TERM_CMD_DECSPPCS: + return screen_DECSPPCS(screen, seq); + case TERM_CMD_DECSPRTT: + return screen_DECSPRTT(screen, seq); + case TERM_CMD_DECSR: + return screen_DECSR(screen, seq); + case TERM_CMD_DECSRFR: + return screen_DECSRFR(screen, seq); + case TERM_CMD_DECSSCLS: + return screen_DECSSCLS(screen, seq); + case TERM_CMD_DECSSDT: + return screen_DECSSDT(screen, seq); + case TERM_CMD_DECSSL: + return screen_DECSSL(screen, seq); + case TERM_CMD_DECST8C: + return screen_DECST8C(screen, seq); + case TERM_CMD_DECSTBM: + return screen_DECSTBM(screen, seq); + case TERM_CMD_DECSTR: + return screen_DECSTR(screen, seq); + case TERM_CMD_DECSTRL: + return screen_DECSTRL(screen, seq); + case TERM_CMD_DECSWBV: + return screen_DECSWBV(screen, seq); + case TERM_CMD_DECSWL: + return screen_DECSWL(screen, seq); + case TERM_CMD_DECTID: + return screen_DECTID(screen, seq); + case TERM_CMD_DECTME: + return screen_DECTME(screen, seq); + case TERM_CMD_DECTST: + return screen_DECTST(screen, seq); + case TERM_CMD_DL: + return screen_DL(screen, seq); + case TERM_CMD_DSR_ANSI: + return screen_DSR_ANSI(screen, seq); + case TERM_CMD_DSR_DEC: + return screen_DSR_DEC(screen, seq); + case TERM_CMD_ECH: + return screen_ECH(screen, seq); + case TERM_CMD_ED: + return screen_ED(screen, seq); + case TERM_CMD_EL: + return screen_EL(screen, seq); + case TERM_CMD_ENQ: + return screen_ENQ(screen, seq); + case TERM_CMD_EPA: + return screen_EPA(screen, seq); + case TERM_CMD_FF: + return screen_FF(screen, seq); + case TERM_CMD_HPA: + return screen_HPA(screen, seq); + case TERM_CMD_HPR: + return screen_HPR(screen, seq); + case TERM_CMD_HT: + return screen_HT(screen, seq); + case TERM_CMD_HTS: + return screen_HTS(screen, seq); + case TERM_CMD_HVP: + return screen_HVP(screen, seq); + case TERM_CMD_ICH: + return screen_ICH(screen, seq); + case TERM_CMD_IL: + return screen_IL(screen, seq); + case TERM_CMD_IND: + return screen_IND(screen, seq); + case TERM_CMD_LF: + return screen_LF(screen, seq); + case TERM_CMD_LS1R: + return screen_LS1R(screen, seq); + case TERM_CMD_LS2: + return screen_LS2(screen, seq); + case TERM_CMD_LS2R: + return screen_LS2R(screen, seq); + case TERM_CMD_LS3: + return screen_LS3(screen, seq); + case TERM_CMD_LS3R: + return screen_LS3R(screen, seq); + case TERM_CMD_MC_ANSI: + return screen_MC_ANSI(screen, seq); + case TERM_CMD_MC_DEC: + return screen_MC_DEC(screen, seq); + case TERM_CMD_NEL: + return screen_NEL(screen, seq); + case TERM_CMD_NP: + return screen_NP(screen, seq); + case TERM_CMD_NULL: + return screen_NULL(screen, seq); + case TERM_CMD_PP: + return screen_PP(screen, seq); + case TERM_CMD_PPA: + return screen_PPA(screen, seq); + case TERM_CMD_PPB: + return screen_PPB(screen, seq); + case TERM_CMD_PPR: + return screen_PPR(screen, seq); + case TERM_CMD_RC: + return screen_RC(screen, seq); + case TERM_CMD_REP: + return screen_REP(screen, seq); + case TERM_CMD_RI: + return screen_RI(screen, seq); + case TERM_CMD_RIS: + return screen_RIS(screen, seq); + case TERM_CMD_RM_ANSI: + return screen_RM_ANSI(screen, seq); + case TERM_CMD_RM_DEC: + return screen_RM_DEC(screen, seq); + case TERM_CMD_S7C1T: + return screen_S7C1T(screen, seq); + case TERM_CMD_S8C1T: + return screen_S8C1T(screen, seq); + case TERM_CMD_SCS: + return screen_SCS(screen, seq); + case TERM_CMD_SD: + return screen_SD(screen, seq); + case TERM_CMD_SGR: + return screen_SGR(screen, seq); + case TERM_CMD_SI: + return screen_SI(screen, seq); + case TERM_CMD_SM_ANSI: + return screen_SM_ANSI(screen, seq); + case TERM_CMD_SM_DEC: + return screen_SM_DEC(screen, seq); + case TERM_CMD_SO: + return screen_SO(screen, seq); + case TERM_CMD_SPA: + return screen_SPA(screen, seq); + case TERM_CMD_SS2: + return screen_SS2(screen, seq); + case TERM_CMD_SS3: + return screen_SS3(screen, seq); + case TERM_CMD_ST: + return screen_ST(screen, seq); + case TERM_CMD_SU: + return screen_SU(screen, seq); + case TERM_CMD_SUB: + return screen_SUB(screen, seq); + case TERM_CMD_TBC: + return screen_TBC(screen, seq); + case TERM_CMD_VPA: + return screen_VPA(screen, seq); + case TERM_CMD_VPR: + return screen_VPR(screen, seq); + case TERM_CMD_VT: + return screen_VT(screen, seq); + case TERM_CMD_XTERM_CLLHP: + return screen_XTERM_CLLHP(screen, seq); + case TERM_CMD_XTERM_IHMT: + return screen_XTERM_IHMT(screen, seq); + case TERM_CMD_XTERM_MLHP: + return screen_XTERM_MLHP(screen, seq); + case TERM_CMD_XTERM_MUHP: + return screen_XTERM_MUHP(screen, seq); + case TERM_CMD_XTERM_RPM: + return screen_XTERM_RPM(screen, seq); + case TERM_CMD_XTERM_RRV: + return screen_XTERM_RRV(screen, seq); + case TERM_CMD_XTERM_RTM: + return screen_XTERM_RTM(screen, seq); + case TERM_CMD_XTERM_SACL1: + return screen_XTERM_SACL1(screen, seq); + case TERM_CMD_XTERM_SACL2: + return screen_XTERM_SACL2(screen, seq); + case TERM_CMD_XTERM_SACL3: + return screen_XTERM_SACL3(screen, seq); + case TERM_CMD_XTERM_SDCS: + return screen_XTERM_SDCS(screen, seq); + case TERM_CMD_XTERM_SGFX: + return screen_XTERM_SGFX(screen, seq); + case TERM_CMD_XTERM_SPM: + return screen_XTERM_SPM(screen, seq); + case TERM_CMD_XTERM_SRV: + return screen_XTERM_SRV(screen, seq); + case TERM_CMD_XTERM_STM: + return screen_XTERM_STM(screen, seq); + case TERM_CMD_XTERM_SUCS: + return screen_XTERM_SUCS(screen, seq); + case TERM_CMD_XTERM_WM: + return screen_XTERM_WM(screen, seq); + } + + return 0; +} + +int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) { + const uint32_t *ucs4_str; + size_t i, j, ucs4_len; + const term_seq *seq; + int r; + + assert_return(screen, -EINVAL); + + /* Feed bytes into utf8 decoder and handle parsed ucs4 chars. We always + * treat data as UTF-8, but the parser makes sure to fall back to raw + * 8bit mode if the stream is not valid UTF-8. This should be more than + * enough to support old 7bit/8bit modes. */ + for (i = 0; i < size; ++i) { + ucs4_str = term_utf8_decode(&screen->utf8, &ucs4_len, in[i]); + for (j = 0; j < ucs4_len; ++j) { + r = term_parser_feed(screen->parser, &seq, ucs4_str[j]); + if (r < 0) { + return r; + } else if (r != TERM_SEQ_NONE) { + r = screen_feed_cmd(screen, seq); + if (r < 0) + return r; + } + } + } + + return 0; +} + +int term_screen_feed_keyboard(term_screen *screen, uint32_t keysym, uint32_t ascii, uint32_t ucs4, unsigned int mods) { + assert_return(screen, -EINVAL); + + /* TODO */ + + return 0; +} + +int term_screen_resize(term_screen *screen, unsigned int x, unsigned int y) { + unsigned int i; + uint8_t *t; + int r; + + assert_return(screen, -EINVAL); + + r = term_page_reserve(screen->page_main, x, y, &screen->attr, screen->age); + if (r < 0) + return r; + + r = term_page_reserve(screen->page_alt, x, y, &screen->attr, screen->age); + if (r < 0) + return r; + + if (x > screen->n_tabs) { + t = realloc(screen->tabs, (x + 7) / 8); + if (!t) + return -ENOMEM; + + screen->tabs = t; + screen->n_tabs = x; + } + + for (i = (screen->page->width + 7) / 8 * 8; i < x; i += 8) + screen->tabs[i / 8] = 0x1; + + term_page_resize(screen->page_main, x, y, &screen->attr, screen->age, screen->history); + term_page_resize(screen->page_alt, x, y, &screen->attr, screen->age, NULL); + + screen->cursor_x = screen_clamp_x(screen, screen->cursor_x); + screen->cursor_y = screen_clamp_x(screen, screen->cursor_y); + screen_cursor_clear_wrap(screen); + + return 0; +} + +void term_screen_soft_reset(term_screen *screen) { + unsigned int i; + + assert(screen); + + screen->gl = &screen->g0; + screen->gr = &screen->g1; + screen->glt = NULL; + screen->grt = NULL; + screen->g0 = &term_unicode_lower; + screen->g1 = &term_unicode_upper; + screen->g2 = &term_unicode_lower; + screen->g3 = &term_unicode_upper; + + screen->page = screen->page_main; + screen->history = screen->history_main; + screen->flags = TERM_FLAG_7BIT_MODE; + screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400; + screen->attr = screen->default_attr; + + screen->saved.cursor_x = 0; + screen->saved.cursor_y = 0; + screen->saved.attr = screen->attr; + screen->saved.gl = screen->gl; + screen->saved.gr = screen->gr; + screen->saved.glt = NULL; + screen->saved.grt = NULL; + screen->flags = 0; + + for (i = 0; i < screen->page->width; i += 8) + screen->tabs[i / 8] = 0x1; + + term_page_set_scroll_region(screen->page_main, 0, screen->page->height); + term_page_set_scroll_region(screen->page_alt, 0, screen->page->height); +} + +void term_screen_hard_reset(term_screen *screen) { + assert(screen); + + term_screen_soft_reset(screen); + zero(screen->utf8); + screen->cursor_x = 0; + screen->cursor_y = 0; + term_page_erase(screen->page_main, 0, 0, screen->page->width, screen->page->height, &screen->attr, screen->age, false); + term_page_erase(screen->page_alt, 0, 0, screen->page->width, screen->page->height, &screen->attr, screen->age, false); +} + +int term_screen_set_answerback(term_screen *screen, const char *answerback) { + char *t = NULL; + + assert_return(screen, -EINVAL); + + if (answerback) { + t = strdup(answerback); + if (!t) + return -ENOMEM; + } + + free(screen->answerback); + screen->answerback = t; + + return 0; +} |