diff --git a/src/vte.c b/src/vte.c index 2da49b3..476ddb9 100644 --- a/src/vte.c +++ b/src/vte.c @@ -26,8 +26,11 @@ /* * Virtual Terminal Emulator - * This is a vt100 implementation. It is written from scratch. It uses the - * console subsystem as output and is tightly bound to it. + * This is the VT implementation. It is written from scratch. It uses the + * console subsystem as output and is tightly bound to it. It supports + * functionality from vt100 up to vt500 series. It doesn't implement an + * explicitly selected terminal but tries to support the most important commands + * to be compatible with existing implementations. */ #include @@ -43,19 +46,46 @@ /* Input parser states */ enum parser_state { - STATE_NORMAL, /* normal mode */ - STATE_ESC, /* starting escape sequence */ - STATE_CSI, /* Control Sequence Introducer */ - STATE_OTHER, /* other known but unimpl escp seqs */ + STATE_NONE, /* placeholder */ + STATE_GROUND, /* initial state and ground */ + STATE_ESC, /* ESC sequence was started */ + STATE_ESC_INT, /* intermediate escape characters */ + STATE_CSI_ENTRY, /* starting CSI sequence */ + STATE_CSI_PARAM, /* CSI parameters */ + STATE_CSI_INT, /* intermediate CSI characters */ + STATE_CSI_IGNORE, /* CSI error; ignore this CSI sequence */ + STATE_DCS_ENTRY, /* starting DCS sequence */ + STATE_DCS_PARAM, /* DCS parameters */ + STATE_DCS_INT, /* intermediate DCS characters */ + STATE_DCS_PASS, /* DCS data passthrough */ + STATE_DCS_IGNORE, /* DCS error; ignore this DCS sequence */ + STATE_OSC_STRING, /* parsing OCS sequence */ + STATE_ST_IGNORE, /* unimplemented seq; ignore until ST */ + STATE_NUM }; -/* maximum CSI parameters */ -#define CSI_ARG_MAX 15 +/* Input parser actions */ +enum parser_action { + ACTION_NONE, /* placeholder */ + ACTION_IGNORE, /* ignore the character entirely */ + ACTION_PRINT, /* print the character on the console */ + ACTION_EXECUTE, /* execute single control character (C0/C1) */ + ACTION_CLEAR, /* clear current parameter state */ + ACTION_COLLECT, /* collect intermediate character */ + ACTION_PARAM, /* collect parameter character */ + ACTION_ESC_DISPATCH, /* dispatch escape sequence */ + ACTION_CSI_DISPATCH, /* dispatch csi sequence */ + ACTION_DCS_START, /* start of DCS data */ + ACTION_DCS_COLLECT, /* collect DCS data */ + ACTION_DCS_END, /* end of DCS data */ + ACTION_OSC_START, /* start of OSC data */ + ACTION_OSC_COLLECT, /* collect OSC data */ + ACTION_OSC_END, /* end of OSC data */ + ACTION_NUM +}; -/* CSI flags */ -#define CSI_START 0x01 /* no arg has been parsed yet */ -#define CSI_QUESTION 0x02 /* ? flag */ -#define CSI_BANG 0x04 /* ! flag */ +/* max CSI arguments */ +#define CSI_ARG_MAX 16 struct kmscon_vte { unsigned long ref; @@ -65,12 +95,9 @@ struct kmscon_vte { const char *kbd_sym; struct kmscon_utf8_mach *mach; - struct { - unsigned int state; - unsigned int csi_flags; - unsigned int csi_argc; - unsigned int csi_argv[CSI_ARG_MAX]; - } parser; + unsigned int state; + unsigned int csi_argc; + int csi_argv[CSI_ARG_MAX]; }; int kmscon_vte_new(struct kmscon_vte **out, struct kmscon_symbol_table *st) @@ -90,6 +117,7 @@ int kmscon_vte_new(struct kmscon_vte **out, struct kmscon_symbol_table *st) memset(vte, 0, sizeof(*vte)); vte->ref = 1; vte->st = st; + vte->state = STATE_GROUND; ret = kmscon_utf8_mach_new(&vte->mach); if (ret) @@ -138,7 +166,8 @@ void kmscon_vte_bind(struct kmscon_vte *vte, struct kmscon_console *con) kmscon_console_ref(vte->con); } -static void parse_control(struct kmscon_vte *vte, uint32_t ctrl) +/* execute control character (C0 or C1) */ +static void do_execute(struct kmscon_vte *vte, uint32_t ctrl) { switch (ctrl) { case 0x00: /* NUL */ @@ -188,157 +217,526 @@ static void parse_control(struct kmscon_vte *vte, uint32_t ctrl) break; case 0x1b: /* ESC */ /* Invokes an escape sequence */ - vte->parser.state = STATE_ESC; - break; - case 0x7f: /* DEL */ - /* Ignored on input */ break; } } -static void parse_csi(struct kmscon_vte *vte, uint32_t val) +static void do_clear(struct kmscon_vte *vte) +{ + int i; + + vte->csi_argc = 0; + for (i = 0; i < CSI_ARG_MAX; ++i) + vte->csi_argv[i] = -1; +} + +static void do_param(struct kmscon_vte *vte, uint32_t data) { int new; - unsigned int num; - if (vte->parser.csi_flags & CSI_START) { - switch (val) { - case '?': - vte->parser.csi_flags |= CSI_QUESTION; - return; - case '!': - vte->parser.csi_flags |= CSI_BANG; - return; - default: - vte->parser.csi_flags &= ~CSI_START; - } - } - - if (val == ';') { - if (vte->parser.csi_argc < CSI_ARG_MAX) - vte->parser.csi_argc++; + if (data == ';') { + if (vte->csi_argc < CSI_ARG_MAX) + vte->csi_argc++; return; } - if (val >= '0' && val <= '9') { - if (vte->parser.csi_argc >= CSI_ARG_MAX) - return; - - new = vte->parser.csi_argv[vte->parser.csi_argc]; - new *= 10; - new += val - '0'; - - /* avoid integer overflows */ - if (new > vte->parser.csi_argv[vte->parser.csi_argc]) - vte->parser.csi_argv[vte->parser.csi_argc] = new; - + if (vte->csi_argc >= CSI_ARG_MAX) return; + + /* avoid integer overflows; max allowed value is 16384 anyway */ + if (vte->csi_argv[vte->csi_argc] > 0xffff) + return; + + if (data >= '0' && data <= '9') { + new = vte->csi_argv[vte->csi_argc]; + if (new <= 0) + new = data - '0'; + else + new = new * 10 + data - '0'; + vte->csi_argv[vte->csi_argc] = new; } +} - vte->parser.csi_argc++; - vte->parser.state = STATE_NORMAL; +static void do_csi(struct kmscon_vte *vte, uint32_t data) +{ + int num; - switch (val) { + if (vte->csi_argc < CSI_ARG_MAX) + vte->csi_argc++; + + switch (data) { case 'A': - num = vte->parser.csi_argv[0]; - if (!num) + num = vte->csi_argv[0]; + if (num <= 0) num = 1; kmscon_console_move_up(vte->con, num, false); break; case 'B': - num = vte->parser.csi_argv[0]; - if (!num) + num = vte->csi_argv[0]; + if (num <= 0) num = 1; kmscon_console_move_down(vte->con, num, false); break; case 'C': - num = vte->parser.csi_argv[0]; - if (!num) + num = vte->csi_argv[0]; + if (num <= 0) num = 1; kmscon_console_move_right(vte->con, num); break; case 'D': - num = vte->parser.csi_argv[0]; - if (!num) + num = vte->csi_argv[0]; + if (num <= 0) num = 1; kmscon_console_move_left(vte->con, num); break; case 'J': - if (vte->parser.csi_argc < 1 || - vte->parser.csi_argv[0] == 0) + if (vte->csi_argv[0] <= 0) kmscon_console_erase_cursor_to_screen(vte->con); - else if (vte->parser.csi_argv[0] == 1) + else if (vte->csi_argv[0] == 1) kmscon_console_erase_screen_to_cursor(vte->con); - else if (vte->parser.csi_argv[0] == 2) + else if (vte->csi_argv[0] == 2) kmscon_console_erase_screen(vte->con); break; case 'K': - if (vte->parser.csi_argc < 1 || - vte->parser.csi_argv[0] == 0) + if (vte->csi_argv[0] <= 0) kmscon_console_erase_cursor_to_end(vte->con); - else if (vte->parser.csi_argv[0] == 1) + else if (vte->csi_argv[0] == 1) kmscon_console_erase_home_to_cursor(vte->con); - else if (vte->parser.csi_argv[0] == 2) + else if (vte->csi_argv[0] == 2) kmscon_console_erase_current_line(vte->con); break; default: - log_debug("vte: unhandled CSI sequence %c\n", val); + log_debug("vte: unhandled CSI sequence %c\n", data); } } -static void parse_other(struct kmscon_vte *vte, uint32_t val) -{ - /* TODO: make this more sophisticated */ - vte->parser.state = STATE_NORMAL; -} - -static void parse_esc(struct kmscon_vte *vte, uint32_t val) -{ - switch (val) { - case '[': /* CSI */ - vte->parser.state = STATE_CSI; - vte->parser.csi_flags = CSI_START; - vte->parser.csi_argc = 0; - memset(vte->parser.csi_argv, 0, - sizeof(vte->parser.csi_argv)); - break; - case 'P': /* DCS */ - case ']': /* OCS */ - case '^': /* PM */ - case '_': /* APC */ - case '#': /* special */ - case '(': /* G0 */ - case ')': /* G1 */ - case '%': /* charset */ - default: - vte->parser.state = STATE_OTHER; - break; - } -} - -static void parse_input(struct kmscon_vte *vte, uint32_t val) +/* perform parser action */ +static void do_action(struct kmscon_vte *vte, uint32_t data, int action) { kmscon_symbol_t sym; - if (val < 0x20) { - parse_control(vte, val); + switch (action) { + case ACTION_NONE: + /* do nothing */ + return; + case ACTION_IGNORE: + /* ignore character */ + break; + case ACTION_PRINT: + sym = kmscon_symbol_make(data); + kmscon_console_write(vte->con, sym); + break; + case ACTION_EXECUTE: + do_execute(vte, data); + break; + case ACTION_CLEAR: + do_clear(vte); + break; + case ACTION_COLLECT: + break; + case ACTION_PARAM: + do_param(vte, data); + break; + case ACTION_ESC_DISPATCH: + break; + case ACTION_CSI_DISPATCH: + do_csi(vte, data); + break; + case ACTION_DCS_START: + break; + case ACTION_DCS_COLLECT: + break; + case ACTION_DCS_END: + break; + case ACTION_OSC_START: + break; + case ACTION_OSC_COLLECT: + break; + case ACTION_OSC_END: + break; + default: + log_warn("vte: invalid action %d\n", action); + } +} + +/* entry actions to be performed when entering the selected state */ +static int entry_action[] = { + [STATE_CSI_ENTRY] = ACTION_CLEAR, + [STATE_DCS_ENTRY] = ACTION_CLEAR, + [STATE_DCS_PASS] = ACTION_DCS_START, + [STATE_ESC] = ACTION_CLEAR, + [STATE_OSC_STRING] = ACTION_OSC_START, + [STATE_NUM] = ACTION_NONE, +}; + +/* exit actions to be performed when leaving the selected state */ +static int exit_action[] = { + [STATE_DCS_PASS] = ACTION_DCS_END, + [STATE_OSC_STRING] = ACTION_OSC_END, + [STATE_NUM] = ACTION_NONE, +}; + +/* perform state transision and dispatch related actions */ +static void do_trans(struct kmscon_vte *vte, uint32_t data, int state, int act) +{ + if (state != STATE_NONE) { + /* A state transition occurs. Perform exit-action, + * transition-action and entry-action. Even when performing a + * transition to the same state as the current state we do this. + * Use STATE_NONE if this is not the desired behavior. + */ + do_action(vte, data, exit_action[vte->state]); + do_action(vte, data, act); + do_action(vte, data, entry_action[state]); + vte->state = state; + } else { + do_action(vte, data, act); + } +} + +/* + * Escape sequence parser + * This parses the new input character \data. It performs state transition and + * calls the right callbacks for each action. + */ +static void parse_data(struct kmscon_vte *vte, uint32_t raw) +{ + /* events that may occur in any state */ + switch (raw) { + case 0x18: + case 0x1a: + case 0x80 ... 0x8f: + case 0x91 ... 0x97: + case 0x99: + case 0x9a: + case 0x9c: + do_trans(vte, raw, STATE_GROUND, ACTION_EXECUTE); + return; + case 0x1b: + do_trans(vte, raw, STATE_ESC, ACTION_NONE); + return; + case 0x98: + case 0x9e: + case 0x9f: + do_trans(vte, raw, STATE_ST_IGNORE, ACTION_NONE); + return; + case 0x90: + do_trans(vte, raw, STATE_DCS_ENTRY, ACTION_NONE); + return; + case 0x9d: + do_trans(vte, raw, STATE_OSC_STRING, ACTION_NONE); + return; + case 0x9b: + do_trans(vte, raw, STATE_CSI_ENTRY, ACTION_NONE); + return; + } + + /* events that depend on the current state */ + switch (vte->state) { + case STATE_GROUND: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + case 0x80 ... 0x8f: + case 0x91 ... 0x9a: + case 0x9c: + do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); + return; + case 0x20 ... 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_PRINT); + return; + } + do_trans(vte, raw, STATE_NONE, ACTION_PRINT); + return; + case STATE_ESC: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); + return; + case 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x20 ... 0x2f: + do_trans(vte, raw, STATE_ESC_INT, ACTION_COLLECT); + return; + case 0x30 ... 0x4f: + case 0x51 ... 0x57: + case 0x59: + case 0x5a: + case 0x5c: + case 0x60 ... 0x7e: + do_trans(vte, raw, STATE_GROUND, ACTION_ESC_DISPATCH); + return; + case 0x5b: + do_trans(vte, raw, STATE_CSI_ENTRY, ACTION_NONE); + return; + case 0x5d: + do_trans(vte, raw, STATE_OSC_STRING, ACTION_NONE); + return; + case 0x50: + do_trans(vte, raw, STATE_DCS_ENTRY, ACTION_NONE); + return; + case 0x58: + case 0x5e: + case 0x5f: + do_trans(vte, raw, STATE_ST_IGNORE, ACTION_NONE); + return; + } + do_trans(vte, raw, STATE_ESC_INT, ACTION_COLLECT); + return; + case STATE_ESC_INT: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); + return; + case 0x20 ... 0x2f: + do_trans(vte, raw, STATE_NONE, ACTION_COLLECT); + return; + case 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x30 ... 0x7e: + do_trans(vte, raw, STATE_GROUND, ACTION_ESC_DISPATCH); + return; + } + do_trans(vte, raw, STATE_NONE, ACTION_COLLECT); + return; + case STATE_CSI_ENTRY: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); + return; + case 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x20 ... 0x2f: + do_trans(vte, raw, STATE_CSI_INT, ACTION_COLLECT); + return; + case 0x3a: + do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); + return; + case 0x30 ... 0x39: + case 0x3b: + do_trans(vte, raw, STATE_CSI_PARAM, ACTION_PARAM); + return; + case 0x3c ... 0x3f: + do_trans(vte, raw, STATE_CSI_PARAM, ACTION_COLLECT); + return; + case 0x40 ... 0x7e: + do_trans(vte, raw, STATE_GROUND, ACTION_CSI_DISPATCH); + return; + } + do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); + return; + case STATE_CSI_PARAM: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); + return; + case 0x30 ... 0x39: + case 0x3b: + do_trans(vte, raw, STATE_NONE, ACTION_PARAM); + return; + case 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x3a: + case 0x3c ... 0x3f: + do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); + return; + case 0x20 ... 0x2f: + do_trans(vte, raw, STATE_CSI_INT, ACTION_COLLECT); + return; + case 0x40 ... 0x7e: + do_trans(vte, raw, STATE_GROUND, ACTION_CSI_DISPATCH); + return; + } + do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); + return; + case STATE_CSI_INT: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); + return; + case 0x20 ... 0x2f: + do_trans(vte, raw, STATE_NONE, ACTION_COLLECT); + return; + case 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x30 ... 0x3f: + do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); + return; + case 0x40 ... 0x7e: + do_trans(vte, raw, STATE_GROUND, ACTION_CSI_DISPATCH); + return; + } + do_trans(vte, raw, STATE_CSI_IGNORE, ACTION_NONE); + return; + case STATE_CSI_IGNORE: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + do_trans(vte, raw, STATE_NONE, ACTION_EXECUTE); + return; + case 0x20 ... 0x3f: + case 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x40 ... 0x7e: + do_trans(vte, raw, STATE_GROUND, ACTION_NONE); + return; + } + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case STATE_DCS_ENTRY: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + case 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x3a: + do_trans(vte, raw, STATE_DCS_IGNORE, ACTION_NONE); + return; + case 0x20 ... 0x2f: + do_trans(vte, raw, STATE_DCS_INT, ACTION_COLLECT); + return; + case 0x30 ... 0x39: + case 0x3b: + do_trans(vte, raw, STATE_DCS_PARAM, ACTION_PARAM); + return; + case 0x3c ... 0x3f: + do_trans(vte, raw, STATE_DCS_PARAM, ACTION_COLLECT); + return; + case 0x40 ... 0x7e: + do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); + return; + } + do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); + return; + case STATE_DCS_PARAM: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + case 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x30 ... 0x39: + case 0x3b: + do_trans(vte, raw, STATE_NONE, ACTION_PARAM); + return; + case 0x3a: + case 0x3c ... 0x3f: + do_trans(vte, raw, STATE_DCS_IGNORE, ACTION_NONE); + return; + case 0x20 ... 0x2f: + do_trans(vte, raw, STATE_DCS_INT, ACTION_COLLECT); + return; + case 0x40 ... 0x7e: + do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); + return; + } + do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); + return; + case STATE_DCS_INT: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + case 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x20 ... 0x2f: + do_trans(vte, raw, STATE_NONE, ACTION_COLLECT); + return; + case 0x30 ... 0x3f: + do_trans(vte, raw, STATE_DCS_IGNORE, ACTION_NONE); + return; + case 0x40 ... 0x7e: + do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); + return; + } + do_trans(vte, raw, STATE_DCS_PASS, ACTION_NONE); + return; + case STATE_DCS_PASS: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + case 0x20 ... 0x7e: + do_trans(vte, raw, STATE_NONE, ACTION_DCS_COLLECT); + return; + case 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x9c: + do_trans(vte, raw, STATE_GROUND, ACTION_NONE); + return; + } + do_trans(vte, raw, STATE_NONE, ACTION_DCS_COLLECT); + return; + case STATE_DCS_IGNORE: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + case 0x20 ... 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x9c: + do_trans(vte, raw, STATE_GROUND, ACTION_NONE); + return; + } + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case STATE_OSC_STRING: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x20 ... 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_OSC_COLLECT); + return; + case 0x9c: + do_trans(vte, raw, STATE_GROUND, ACTION_NONE); + return; + } + do_trans(vte, raw, STATE_NONE, ACTION_OSC_COLLECT); + return; + case STATE_ST_IGNORE: + switch (raw) { + case 0x00 ... 0x17: + case 0x19: + case 0x1c ... 0x1f: + case 0x20 ... 0x7f: + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); + return; + case 0x9c: + do_trans(vte, raw, STATE_GROUND, ACTION_NONE); + return; + } + do_trans(vte, raw, STATE_NONE, ACTION_IGNORE); return; } - switch (vte->parser.state) { - case STATE_ESC: - parse_esc(vte, val); - return; - case STATE_CSI: - parse_csi(vte, val); - return; - case STATE_OTHER: - parse_other(vte, val); - return; - } - - sym = kmscon_symbol_make(val); - kmscon_console_write(vte->con, sym); + log_warn("vte: unhandled input %u in state %d\n", raw, vte->state); } void kmscon_vte_input(struct kmscon_vte *vte, const char *u8, size_t len) @@ -354,7 +752,7 @@ void kmscon_vte_input(struct kmscon_vte *vte, const char *u8, size_t len) if (state == KMSCON_UTF8_ACCEPT || state == KMSCON_UTF8_REJECT) { ucs4 = kmscon_utf8_mach_get(vte->mach); - parse_input(vte, ucs4); + parse_data(vte, ucs4); } } }