Terminal emulator with all the bells and whistles https://www.enlightenment.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1932 lines
59 KiB

#include "private.h"
#include <Elementary.h>
7 years ago
#include <stdint.h>
#include "termio.h"
#include "termpty.h"
#include "termptydbl.h"
#include "termptyesc.h"
#include "termptyops.h"
#include "termptyext.h"
#if defined(SUPPORT_80_132_COLUMNS)
#include "termio.h"
#endif
#undef CRITICAL
#undef ERR
#undef WRN
#undef INF
#undef DBG
#define CRITICAL(...) EINA_LOG_DOM_CRIT(_termpty_log_dom, __VA_ARGS__)
#define ERR(...) EINA_LOG_DOM_ERR(_termpty_log_dom, __VA_ARGS__)
#define WRN(...) EINA_LOG_DOM_WARN(_termpty_log_dom, __VA_ARGS__)
#define INF(...) EINA_LOG_DOM_INFO(_termpty_log_dom, __VA_ARGS__)
#define DBG(...) EINA_LOG_DOM_DBG(_termpty_log_dom, __VA_ARGS__)
#define ST 0x9c // String Terminator
#define BEL 0x07 // Bell
#define ESC 033 // Escape
#define DEL 127
/* XXX: all handle_ functions return the number of bytes successfully read, 0
* if not enough bytes could be read
*/
static const char *ASCII_CHARS_TABLE[] =
{
"NUL", // '\0'
"SOH", // '\001'
"STX", // '\002'
"ETX", // '\003'
"EOT", // '\004'
"ENQ", // '\005'
"ACK", // '\006'
"BEL", // '\007'
"BS", // '\010'
"HT", // '\011'
"LF", // '\012'
"VT", // '\013'
"FF", // '\014'
"CR" , // '\015'
"SO", // '\016'
"SI", // '\017'
"DLE", // '\020'
"DC1", // '\021'
"DC2", // '\022'
"DC3", // '\023'
"DC4", // '\024'
"NAK", // '\025'
"SYN", // '\026'
"ETB", // '\027'
"CAN", // '\030'
"EM", // '\031'
"SUB", // '\032'
"ESC", // '\033'
"FS", // '\034'
"GS", // '\035'
"RS", // '\036'
"US" // '\037'
};
static const char *
_safechar(unsigned int c)
{
static char _str[9];
// This should avoid 'BEL' and 'ESC' in particular, which would
// have side effects in the parent terminal (esp. ESC).
if (c < (sizeof(ASCII_CHARS_TABLE) / sizeof(ASCII_CHARS_TABLE[0])))
return ASCII_CHARS_TABLE[c];
if (c == DEL)
return "DEL";
// The rest should be safe (?)
snprintf(_str, 9, "%c", c);
_str[8] = '\0';
return _str;
}
static int
_csi_arg_get(Eina_Unicode **ptr)
{
Eina_Unicode *b = *ptr;
int sum = 0;
while ((*b) && (*b < '0' || *b > '9')) b++;
if (!*b)
{
*ptr = NULL;
return 0;
}
while ((*b >= '0') && (*b <= '9'))
{
7 years ago
if (sum > INT32_MAX/10 )
{
*ptr = NULL;
return 0;
}
sum *= 10;
sum += *b - '0';
b++;
}
*ptr = b;
return sum;
}
static void
_handle_cursor_control(Termpty *ty, const Eina_Unicode *cc)
{
switch (*cc)
{
case 0x07: // BEL '\a' (bell)
ty->termstate.had_cr = 0;
if (ty->cb.bell.func) ty->cb.bell.func(ty->cb.bell.data);
return;
case 0x08: // BS '\b' (backspace)
DBG("->BS");
ty->termstate.had_cr = 0;
ty->termstate.wrapnext = 0;
ty->cursor_state.cx--;
TERMPTY_RESTRICT_FIELD(ty->cursor_state.cx, 0, ty->w);
return;
case 0x09: // HT '\t' (horizontal tab)
DBG("->HT");
ty->termstate.had_cr = 0;
TERMPTY_SCREEN(ty, ty->cursor_state.cx, ty->cursor_state.cy).att.tab = 1;
ty->termstate.wrapnext = 0;
ty->cursor_state.cx += 8;
ty->cursor_state.cx = (ty->cursor_state.cx / 8) * 8;
TERMPTY_RESTRICT_FIELD(ty->cursor_state.cx, 0, ty->w);
return;
case 0x0a: // LF '\n' (new line)
case 0x0b: // VT '\v' (vertical tab)
case 0x0c: // FF '\f' (form feed)
DBG("->LF");
if (ty->termstate.had_cr)
{
TERMPTY_SCREEN(ty, ty->termstate.had_cr_x,
ty->termstate.had_cr_y).att.newline = 1;
}
ty->termstate.had_cr = 0;
ty->termstate.wrapnext = 0;
if (ty->termstate.crlf) ty->cursor_state.cx = 0;
ty->cursor_state.cy++;
termpty_text_scroll_test(ty, EINA_TRUE);
return;
case 0x0d: // CR '\r' (carriage ret)
DBG("->CR");
if (ty->cursor_state.cx != 0)
{
ty->termstate.had_cr_x = ty->cursor_state.cx;
ty->termstate.had_cr_y = ty->cursor_state.cy;
ty->termstate.wrapnext = 0;
}
ty->cursor_state.cx = 0;
// ty->termstate.had_cr = 1;
return;
default:
return;
}
}
static void
_switch_to_alternative_screen(Termpty *ty, int mode)
{
DBG("switch to alternative screen, mode:%d", mode);
if (ty->altbuf)
{
// if we are looking at alt buf now,
// clear main buf before we swap it back
// into the screen2 save (so save is
// clear)
termpty_clear_all(ty);
}
// swap screen content now
if (mode != ty->altbuf)
termpty_screen_swap(ty);
}
static void
_handle_esc_csi_reset_mode(Termpty *ty, Eina_Unicode cc, Eina_Unicode *b)
{
int mode = 0, priv = 0, arg;
if (cc == 'h') mode = 1;
if (*b == '?')
{
priv = 1;
b++;
}
if (priv) /* DEC Private Mode Reset (DECRST) */
{
while (b)
{
arg = _csi_arg_get(&b);
if (b)
{
// complete-ish list here:
// http://ttssh2.sourceforge.jp/manual/en/about/ctrlseq.html
switch (arg)
{
case 1:
ty->termstate.appcursor = mode;
break;
case 2:
ty->termstate.kbd_lock = mode;
break;
case 3: // 132 column mode… should we handle this?
#if defined(SUPPORT_80_132_COLUMNS)
if (ty->termstate.att.is_80_132_mode_allowed)
{
/* ONLY FOR TESTING PURPOSE FTM */
Evas_Object *wn;
int w, h;
wn = termio_win_get(ty->obj);
elm_win_size_step_get(wn, &w, &h);
evas_object_resize(wn,
4 +
(mode ? 132 : 80) * w,
4 + ty->h * h);
termpty_resize(ty, mode ? 132 : 80,
ty->h);
termpty_reset_state(ty);
termpty_clear_screen(ty,
TERMPTY_CLR_ALL);
}
#endif
break;
case 4:
WRN("TODO: scrolling mode (DECSCLM): %i", mode);
break;
case 5:
ty->termstate.reverse = mode;
break;
case 6:
if (mode)
{
ty->termstate.margin_top = ty->cursor_state.cy;
ty->cursor_state.cx = 0;
}
else
{
ty->cursor_state.cx = 0;
ty->termstate.margin_top = 0;
}
DBG("origin mode (%d): cursor is at 0,0"
" cursor limited to screen/start point"
" for line #'s depends on top margin",
mode);
break;
case 7:
DBG("set wrap mode to %i", mode);
ty->termstate.wrap = mode;
break;
case 8:
ty->termstate.no_autorepeat = !mode;
DBG("auto repeat %i", mode);
break;
case 9:
DBG("set mouse (X10) %i", mode);
if (mode) ty->mouse_mode = MOUSE_X10;
else ty->mouse_mode = MOUSE_OFF;
break;
case 12: // ignore
WRN("TODO: set blinking cursor to (stop?) %i or local echo (ignored)", mode);
break;
case 19: // never seen this - what to do?
WRN("TODO: set print extent to full screen");
break;
case 20: // crfl==1 -> cur moves to col 0 on LF, FF or VT, ==0 -> mode is cr+lf
ty->termstate.crlf = mode;
break;
case 25:
ty->termstate.hide_cursor = !mode;
DBG("hide cursor: %d", !mode);
break;
case 30: // ignore
WRN("TODO: set scrollbar mapping %i", mode);
break;
case 33: // ignore
WRN("TODO: Stop cursor blink %i", mode);
break;
case 34: // ignore
WRN("TODO: Underline cursor mode %i", mode);
break;
case 35: // ignore
WRN("TODO: set shift keys %i", mode);
break;
case 38: // ignore
WRN("TODO: switch to tek window %i", mode);
break;
case 40:
DBG("Allow 80 -> 132 Mode %i", mode);
#if defined(SUPPORT_80_132_COLUMNS)
ty->termstate.att.is_80_132_mode_allowed = mode;
#endif
break;
case 45: // ignore
WRN("TODO: Reverse-wraparound Mode");
break;
case 59: // ignore
WRN("TODO: kanji terminal mode %i", mode);
break;
case 66:
WRN("TODO: app keypad mode %i", mode);
break;
case 67:
ty->termstate.send_bs = mode;
DBG("backspace send bs not del = %i", mode);
break;
case 1000:
if (mode) ty->mouse_mode = MOUSE_NORMAL;
else ty->mouse_mode = MOUSE_OFF;
DBG("set mouse (press+release only) to %i", mode);
break;
case 1001:
WRN("TODO: x11 mouse highlighting %i", mode);
break;
case 1002:
if (mode) ty->mouse_mode = MOUSE_NORMAL_BTN_MOVE;
else ty->mouse_mode = MOUSE_OFF;
DBG("set mouse (press+release+motion while pressed) %i", mode);
break;
case 1003:
if (mode) ty->mouse_mode = MOUSE_NORMAL_ALL_MOVE;
else ty->mouse_mode = MOUSE_OFF;
DBG("set mouse (press+release+all motion) %i", mode);
break;
case 1004: // i dont know what focus repporting is?
WRN("TODO: enable focus reporting %i", mode);
break;
case 1005:
if (mode) ty->mouse_ext = MOUSE_EXT_UTF8;
else ty->mouse_ext = MOUSE_EXT_NONE;
DBG("set mouse (xterm utf8 style) %i", mode);
break;
case 1006:
if (mode) ty->mouse_ext = MOUSE_EXT_SGR;
else ty->mouse_ext = MOUSE_EXT_NONE;
DBG("set mouse (xterm sgr style) %i", mode);
break;
case 1010: // ignore
WRN("TODO: set home on tty output %i", mode);
break;
case 1012: // ignore
WRN("TODO: set home on tty input %i", mode);
break;
case 1015:
if (mode) ty->mouse_ext = MOUSE_EXT_URXVT;
else ty->mouse_ext = MOUSE_EXT_NONE;
DBG("set mouse (rxvt-unicode style) %i", mode);
break;
case 1034: // ignore
/* libreadline6 emits it but it shouldn't.
See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=577012
*/
DBG("Ignored screen mode %i", arg);
break;
case 1047:
case 47:
_switch_to_alternative_screen(ty, mode);
break;
case 1048:
case 1049:
termpty_cursor_copy(ty, mode);
if (arg == 1049)
_switch_to_alternative_screen(ty, mode);
break;
case 2004:
ty->bracketed_paste = mode;
break;
case 7727: // ignore
WRN("TODO: enable application escape mode %i", mode);
break;
case 7786: // ignore
WRN("TODO: enable mouse wheel -> cursor key xlation %i", mode);
break;
default:
WRN("Unhandled DEC Private Reset Mode arg %i", arg);
break;
}
}
}
}
else /* Reset Mode (RM) */
{
while (b)
{
arg = _csi_arg_get(&b);
if (b)
{
switch (arg)
{
case 1:
ty->termstate.appcursor = mode;
break;
case 4:
DBG("set insert mode to %i", mode);
ty->termstate.insert = mode;
break;
case 34:
WRN("TODO: hebrew keyboard mapping: %i", mode);
break;
case 36:
WRN("TODO: hebrew encoding mode: %i", mode);
break;
default:
WRN("Unhandled ANSI Reset Mode arg %i", arg);
}
}
}
}
}
static void
_handle_esc_csi_color_set(Termpty *ty, Eina_Unicode **ptr)
{
Eina_Unicode *b = *ptr;
int first = 1;
if (b && (*b == '>'))
{ // key resources used by xterm
WRN("TODO: set/reset key resources used by xterm");
return;
}
DBG("color set");
while (b)
{
int arg = _csi_arg_get(&b);
if ((first) && (!b))
termpty_reset_att(&(ty->termstate.att));
else if (b)
{
first = 0;
switch (arg)
{
case 0: // reset to normal
termpty_reset_att(&(ty->termstate.att));
break;
case 1: // bold/bright
ty->termstate.att.bold = 1;
break;
case 2: // faint
ty->termstate.att.faint = 1;
break;
case 3: // italic
ty->termstate.att.italic = 1;
break;
case 4: // underline
ty->termstate.att.underline = 1;
break;
case 5: // blink
ty->termstate.att.blink = 1;
break;
case 6: // blink rapid
ty->termstate.att.blink2 = 1;
break;
case 7: // reverse
ty->termstate.att.inverse = 1;
break;
case 8: // invisible
ty->termstate.att.invisible = 1;
break;
case 9: // strikethrough
ty->termstate.att.strike = 1;
break;
case 20: // fraktur!
ty->termstate.att.fraktur = 1;
break;
case 21: // no bold/bright
ty->termstate.att.bold = 0;
break;
case 22: // no bold/bright, no faint
ty->termstate.att.bold = 0;
ty->termstate.att.faint = 0;
break;
case 23: // no italic, not fraktur
ty->termstate.att.italic = 0;
ty->termstate.att.fraktur = 0;
break;
case 24: // no underline
ty->termstate.att.underline = 0;
break;
case 25: // no blink
ty->termstate.att.blink = 0;
ty->termstate.att.blink2 = 0;
break;
case 27: // no reverse
ty->termstate.att.inverse = 0;
break;
case 28: // no invisible
ty->termstate.att.invisible = 0;
break;
case 29: // no strikethrough
ty->termstate.att.strike = 0;
break;
case 30: // fg
case 31:
case 32:
case 33:
case 34:
case 35:
case 36:
case 37:
ty->termstate.att.fg256 = 0;
ty->termstate.att.fg = (arg - 30) + COL_BLACK;
ty->termstate.att.fgintense = 0;
break;
case 38: // xterm 256 fg color ???
// now check if next arg is 5
arg = _csi_arg_get(&b);
if (arg != 5) ERR("Failed xterm 256 color fg esc 5 (got %d)", arg);
else
{
// then get next arg - should be color index 0-255
arg = _csi_arg_get(&b);
if (!b) ERR("Failed xterm 256 color fg esc val");
else
{
ty->termstate.att.fg256 = 1;
ty->termstate.att.fg = arg;
}
}
ty->termstate.att.fgintense = 0;
break;
case 39: // default fg color
ty->termstate.att.fg256 = 0;
ty->termstate.att.fg = COL_DEF;
ty->termstate.att.fgintense = 0;
break;
case 40: // bg
case 41:
case 42:
case 43:
case 44:
case 45:
case 46:
case 47:
ty->termstate.att.bg256 = 0;
ty->termstate.att.bg = (arg - 40) + COL_BLACK;
ty->termstate.att.bgintense = 0;
break;
case 48: // xterm 256 bg color ???
// now check if next arg is 5
arg = _csi_arg_get(&b);
if (arg != 5) ERR("Failed xterm 256 color bg esc 5 (got %d)", arg);
else
{
// then get next arg - should be color index 0-255
arg = _csi_arg_get(&b);
if (!b) ERR("Failed xterm 256 color bg esc val");
else
{
ty->termstate.att.bg256 = 1;
ty->termstate.att.bg = arg;
}
}
ty->termstate.att.bgintense = 0;
break;
case 49: // default bg color
ty->termstate.att.bg256 = 0;
ty->termstate.att.bg = COL_DEF;
ty->termstate.att.bgintense = 0;
break;
case 90: // fg
case 91:
case 92:
case 93:
case 94:
case 95:
case 96:
case 97:
ty->termstate.att.fg256 = 0;
ty->termstate.att.fg = (arg - 90) + COL_BLACK;
ty->termstate.att.fgintense = 1;
break;
case 98: // xterm 256 fg color ???
// now check if next arg is 5
arg = _csi_arg_get(&b);
if (arg != 5) ERR("Failed xterm 256 color fg esc 5 (got %d)", arg);
else
{
// then get next arg - should be color index 0-255
arg = _csi_arg_get(&b);
if (!b) ERR("Failed xterm 256 color fg esc val");
else
{
ty->termstate.att.fg256 = 1;
ty->termstate.att.fg = arg;
}
}
ty->termstate.att.fgintense = 1;
break;
case 99: // default fg color
ty->termstate.att.fg256 = 0;
ty->termstate.att.fg = COL_DEF;
ty->termstate.att.fgintense = 1;
break;
case 100: // bg
case 101:
case 102:
case 103:
case 104:
case 105:
case 106:
case 107:
ty->termstate.att.bg256 = 0;
ty->termstate.att.bg = (arg - 100) + COL_BLACK;
ty->termstate.att.bgintense = 1;
break;
case 108: // xterm 256 bg color ???
// now check if next arg is 5
arg = _csi_arg_get(&b);
if (arg != 5) ERR("Failed xterm 256 color bg esc 5 (got %d)", arg);
else
{
// then get next arg - should be color index 0-255
arg = _csi_arg_get(&b);
if (!b) ERR("Failed xterm 256 color bg esc val");
else
{
ty->termstate.att.bg256 = 1;
ty->termstate.att.bg = arg;
}
}
ty->termstate.att.bgintense = 1;
break;
case 109: // default bg color
ty->termstate.att.bg256 = 0;
ty->termstate.att.bg = COL_DEF;
ty->termstate.att.bgintense = 1;
break;
default: // not handled???
WRN("Unhandled color cmd [%i]", arg);
break;
}
}
}
}
static void
_handle_esc_csi_dsr(Termpty *ty, Eina_Unicode *b)
{
int arg, len;
char bf[32];
if (*b == '>')
{
WRN("TODO: disable key resources used by xterm");
return;
}
if (*b == '?')
{
b++;
arg = _csi_arg_get(&b);
switch (arg)
{
case 6:
len = snprintf(bf, sizeof(bf), "\033[?%d;%d;1R",
ty->cursor_state.cy + 1, ty->cursor_state.cx + 1);
termpty_write(ty, bf, len);
break;
default:
WRN("unhandled DSR (dec specific) %d", arg);
break;
}
}
else
{
arg = _csi_arg_get(&b);
switch (arg)
{
case 6:
len = snprintf(bf, sizeof(bf), "\033[%d;%dR",
ty->cursor_state.cy + 1, ty->cursor_state.cx + 1);
termpty_write(ty, bf, len);
break;
default:
WRN("unhandled DSR %d", arg);
break;
}
}
}
static int
_handle_esc_csi(Termpty *ty, const Eina_Unicode *c, Eina_Unicode *ce)
{
9 years ago
int arg, i;
const Eina_Unicode *cc, *be;
Eina_Unicode buf[4096], *b;
cc = (Eina_Unicode *)c;
b = buf;
be = buf + sizeof(buf) / sizeof(buf[0]);
while ((cc < ce) && (*cc <= '?') && (b < be))
{
_handle_cursor_control(ty, cc);
*b = *cc;
b++;
cc++;
}
if (b == be)
{
ERR("csi parsing overflowed, skipping the whole buffer (binary data?)");
return cc - c;
}
if (cc == ce) return 0;
*b = 0;
b = buf;
DBG(" CSI: '%s' args '%s'", _safechar(*cc), (char *) buf);
switch (*cc)
{
case 'm': // color set
_handle_esc_csi_color_set(ty, &b);
break;
case '@': // insert N blank chars
arg = _csi_arg_get(&b);
if (arg < 1) arg = 1;
DBG("insert %d blank chars", arg);
{
int pi = ty->termstate.insert;
Eina_Unicode blank[1] = { ' ' };
int cx = ty->cursor_state.cx;
ty->termstate.wrapnext = 0;
ty->termstate.insert = 1;
for (i = 0; i < arg; i++)
termpty_text_append(ty, blank, 1);
ty->termstate.insert = pi;
ty->cursor_state.cx = cx;
TERMPTY_RESTRICT_FIELD(ty->cursor_state.cx, 0, ty->w);
}
break;
case 'A': // cursor up N
case 'e': // cursor up N
arg = _csi_arg_get(&b);
if (arg < 1) arg = 1;
DBG("cursor up %d", arg);
ty->termstate.wrapnext = 0;
ty->cursor_state.cy = MAX(0, ty->cursor_state.cy - arg);
TERMPTY_RESTRICT_FIELD(ty->cursor_state.cy, 0, ty->h);
break;
case 'B': // cursor down N
arg = _csi_arg_get(&b);
if (arg < 1) arg = 1;
DBG("cursor down %d", arg);
ty->termstate.wrapnext = 0;
ty->cursor_state.cy = MIN(ty->h - 1, ty->cursor_state.cy + arg);
TERMPTY_RESTRICT_FIELD(ty->cursor_state.cy, 0, ty->h);
break;
case 'D': // cursor left N
arg = _csi_arg_get(&b);
if (arg < 1) arg = 1;
DBG("cursor left %d", arg);
ty->termstate.wrapnext = 0;
for (i = 0; i < arg; i++)
ty->cursor_state.cx--;
TERMPTY_RESTRICT_FIELD(ty->cursor_state.cx, 0, ty->w);
break;
case 'C': // cursor right N
case 'a': // cursor right N
arg = _csi_arg_get(&b);
if (arg < 1) arg = 1;