/* * Copyright (c) 2021 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "ansi.h" #include "session.h" #include "util.h" /* * We may be outputting a bunch of ansi()s in a string, so they can't all * return the same buffer. Rotate between a few on each iteration. */ static char ansi_buf[10][32]; static short last_ansi_buf = 0; const char * ansi_bold(struct session *s) { static const char bold[] = "\33[1m"; if (s->vt100) return bold; return ""; } const char * ansi_reset(struct session *s) { static const char reset[] = "\33[0m"; if (s->vt100) return reset; return ""; } char * ansi(struct session *s, ...) { char ansi_tmp[10]; char *ansi_out; va_list ap; short attr, val, val2, n; size_t len; ansi_out = (char *)&ansi_buf[last_ansi_buf]; if (++last_ansi_buf >= nitems(ansi_buf)) last_ansi_buf = 0; *ansi_out = '\0'; va_start(ap, s); while ((attr = va_arg(ap, short)) != ANSI_END) { len = 0; switch (attr) { case ANSI_RESET: if (s->vt100) len = strlcat(ansi_out, "\33[0m", sizeof(ansi_buf[0])); break; case ANSI_BOLD: if (s->vt100) len = strlcat(ansi_out, "\33[1m", sizeof(ansi_buf[0])); break; case ANSI_UNDERLINE: if (s->vt100) len = strlcat(ansi_out, "\33[4m", sizeof(ansi_buf[0])); break; case ANSI_REVERSE: if (s->vt100) len = strlcat(ansi_out, "\33[7m", sizeof(ansi_buf[0])); break; case ANSI_CLEAR_SCREEN: if (s->vt100) len = strlcat(ansi_out, "\33[2J", sizeof(ansi_buf[0])); else if (s->vt52) /* XXX: changes cursor position */ len = strlcat(ansi_out, "\33H\33J", sizeof(ansi_buf[0])); break; case ANSI_ERASE_LINE: if (s->vt100) /* XXX: this doesn't work on windows command-line telnet */ len = strlcat(ansi_out, "\33[2K", sizeof(ansi_buf[0])); else if (s->vt52) /* XXX: changes cursor position */ len = strlcat(ansi_out, "\r\33K", sizeof(ansi_buf[0])); break; case ANSI_BACKSPACE: if (s->vt100) len = strlcat(ansi_out, "\33[D \33[D", sizeof(ansi_buf[0])); else if (s->vt52) len = strlcat(ansi_out, "\33D \33D", sizeof(ansi_buf[0])); else len = strlcat(ansi_out, "\10 \10", sizeof(ansi_buf[0])); break; /* these require N args */ case ANSI_DOWN_N: if (s->vt100) { sprintf(ansi_tmp, "\33[%dB", va_arg(ap, short)); len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0])); } else if (s->vt52) { val = va_arg(ap, short); for (n = 0; n < val; n++) len = strlcat(ansi_out, "\33B", sizeof(ansi_buf[0])); } break; case ANSI_FORWARD_N: if (s->vt100) { sprintf(ansi_tmp, "\33[%dC", va_arg(ap, short)); len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0])); } else if (s->vt52) { val = va_arg(ap, short); for (n = 0; n < val; n++) len = strlcat(ansi_out, "\33C", sizeof(ansi_buf[0])); } break; case ANSI_BACK_N: if (s->vt100) { sprintf(ansi_tmp, "\33[%dD", va_arg(ap, short)); len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0])); } else if (s->vt52) { val = va_arg(ap, short); for (n = 0; n < val; n++) len = strlcat(ansi_out, "\33D", sizeof(ansi_buf[0])); } break; case ANSI_UP_N: if (s->vt100) { sprintf(ansi_tmp, "\33[%dF", va_arg(ap, short)); len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0])); } else if (s->vt52) { val = va_arg(ap, short); for (n = 0; n < val; n++) len = strlcat(ansi_out, "\33A", sizeof(ansi_buf[0])); } break; case ANSI_INSERT_LINES_N: if (s->vt100) { sprintf(ansi_tmp, "\33[%dL", va_arg(ap, short)); len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0])); } else if (s->vt52) { val = va_arg(ap, short); for (n = 0; n < val; n++) len = strlcat(ansi_out, "\33L", sizeof(ansi_buf[0])); } break; case ANSI_DELETE_LINES_N: if (s->vt100) { sprintf(ansi_tmp, "\33[%dM", va_arg(ap, short)); len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0])); } else if (s->vt52) { val = va_arg(ap, short); for (n = 0; n < val; n++) len = strlcat(ansi_out, "\33M", sizeof(ansi_buf[0])); } break; case ANSI_COL_N: val = va_arg(ap, short); if (val == 1) len = strlcat(ansi_out, "\r", sizeof(ansi_buf[0])); else { if (s->vt100) { /* \e[xG not supported on windows telnet, fake it */ sprintf(ansi_tmp, "\r\33[%dC", val); len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0])); } else if (s->vt52) { /* go to beginning of line, then move right N times */ strlcat(ansi_out, "\r", sizeof(ansi_buf[0])); sprintf(ansi_tmp, "\33C"); for (n = 0; n < val; n++) len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0])); } } break; case ANSI_CURSOR_LINE_COL: val = va_arg(ap, short); val2 = va_arg(ap, short); if (s->vt100) /* line, column */ sprintf(ansi_tmp, "\33[%d;%dH", val, val2); else if (s->vt52) /* column, line as chr(col)+31,chr(line)+31 */ sprintf(ansi_tmp, "\33Y%c%c", val2 + 31, val + 31); len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0])); break; case ANSI_SAVE_CURSOR: if (s->vt100) len = strlcat(ansi_out, "\33[s", sizeof(ansi_buf[0])); else if (s->vt52) len = strlcat(ansi_out, "\337", sizeof(ansi_buf[0])); break; case ANSI_RESTORE_SAVED_CURSOR: if (s->vt100) len = strlcat(ansi_out, "\33[u", sizeof(ansi_buf[0])); else if (s->vt52) len = strlcat(ansi_out, "\338", sizeof(ansi_buf[0])); break; default: panic("ansi: Unknown ANSI attribute %d", attr); } if (len > sizeof(ansi_buf[0])) panic("ansi: strlcat overflow; missing ANSI_END?"); } va_end(ap); return ansi_out; } size_t ansi_strip(char *inp, char **outp) { size_t ilen = strlen(inp); size_t n, olen = 0; short in_csi; if (outp) { *outp = xmalloc(ilen + 1); if (*outp == NULL) return 0; } for (n = 0, in_csi = 0; n < ilen; ) { if (in_csi) { /* eat "12;34;56" */ while (n < ilen) { if ((inp[n] >= '0' && inp[n] <= '9') || inp[n] == ';') n++; else break; } /* command character */ if (n < ilen) n++; in_csi = 0; } else if (inp[n] == '\33' && inp[n + 1] == '[') { in_csi = 1; n += 2; } else if (inp[n] == '\r') { /* consider this an ansi character */ n++; } else { if (outp) (*outp)[olen] = inp[n]; olen++; n++; } } return olen; } void ansi_probe_screen_size(struct session *s) { short cols, rows; char c[2] = { 0 }; if (!s->vt100) return; /* save cursor position */ session_clear_input(s); session_printf(s, "\33[s"); /* go to the (hopefully) edge of the screen */ session_printf(s, "\33[200B\33[200C"); session_flush(s); /* report cursor position */ session_clear_input(s); session_printf(s, "\33[6n"); session_flush(s); session_wait_for_chars(s, 1500, 100); /* \e[12;34R */ if (s->ibuf[0] == ESC && s->ibuf[1] == '[' && sscanf((char *)&s->ibuf + 2, "%d;%d%1[R]", &rows, &cols, &c) == 3 && c[0] == 'R') { if (cols >= MIN_TERMINAL_COLUMNS && rows >= MIN_TERMINAL_LINES) { s->terminal_columns = cols; s->terminal_lines = rows; session_logf(s, "Probed terminal size of %dx%d", cols, rows); } else { session_logf(s, "Bogus terminal size probe response: %dx%d", cols, rows); } } /* restore saved cursor position */ session_clear_input(s); session_printf(s, "\33[u"); session_flush(s); }