diff --git a/CMakeLists.txt b/CMakeLists.txt index edf13be..f438edd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,7 @@ set(SOURCE_FILES user/persist.h include/helpers.h user/syscfg.c - user/syscfg.h) + user/syscfg.h user/ansi_utf.c user/ansi_utf.h) include_directories(include) include_directories(user) diff --git a/Makefile b/Makefile index 3ada8cd..6e1a9d1 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,7 @@ CFLAGS = -Os -ggdb -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fn -nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \ -Wno-address -Wno-unused -CFLAGS += -DHTTPD_MAX_BACKLOG_SIZE=8192 +CFLAGS += -DHTTPD_MAX_BACKLOG_SIZE=10240 CFLAGS += -DGIT_HASH='"$(shell git rev-parse --short HEAD)"' CFLAGS += -DADMIN_PASSWORD=$(ADMIN_PASSWORD) diff --git a/html_orig/css/app.css b/html_orig/css/app.css index 6679b1f..24a1467 100644 --- a/html_orig/css/app.css +++ b/html_orig/css/app.css @@ -1152,9 +1152,6 @@ body.term #botnav { position: absolute; top: -9999px; } -.nb { - font-weight: normal !important; } - .theme-0 .fg0 { color: #111213; } .theme-0 .bg0 { @@ -1548,6 +1545,24 @@ body.term #botnav { .bold { font-weight: bold !important; } +.faint span { + opacity: 0.6; } + +.italic { + font-style: italic; } + +.under { + text-decoration: underline; } + +.strike { + text-decoration: line-through; } + +.underline.strike { + text-decoration: underline line-through; } + +.blink-hide .blink { + color: transparent; } + .Row.color-preview { font-family: monospace; font-size: 16pt; diff --git a/html_orig/js/app.js b/html_orig/js/app.js index 1f7fc48..9684664 100644 --- a/html_orig/js/app.js +++ b/html_orig/js/app.js @@ -1022,6 +1022,70 @@ $.ready(function() { }, 1); } }); + + +/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */ +if (!String.fromCodePoint) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var stringFromCharCode = String.fromCharCode; + var floor = Math.floor; + var fromCodePoint = function() { + var MAX_SIZE = 0x4000; + var codeUnits = []; + var highSurrogate; + var lowSurrogate; + var index = -1; + var length = arguments.length; + if (!length) { + return ''; + } + var result = ''; + while (++index < length) { + var codePoint = Number(arguments[index]); + if ( + !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity` + codePoint < 0 || // not a valid Unicode code point + codePoint > 0x10FFFF || // not a valid Unicode code point + floor(codePoint) != codePoint // not an integer + ) { + throw RangeError('Invalid code point: ' + codePoint); + } + if (codePoint <= 0xFFFF) { // BMP code point + codeUnits.push(codePoint); + } else { // Astral code point; split in surrogate halves + // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + codePoint -= 0x10000; + highSurrogate = (codePoint >> 10) + 0xD800; + lowSurrogate = (codePoint % 0x400) + 0xDC00; + codeUnits.push(highSurrogate, lowSurrogate); + } + if (index + 1 == length || codeUnits.length > MAX_SIZE) { + result += stringFromCharCode.apply(null, codeUnits); + codeUnits.length = 0; + } + } + return result; + }; + if (defineProperty) { + defineProperty(String, 'fromCodePoint', { + 'value': fromCodePoint, + 'configurable': true, + 'writable': true + }); + } else { + String.fromCodePoint = fromCodePoint; + } + }()); +} // Generated from PHP locale file var _tr = { "wifi.connected_ip_is": "Connected, IP is ", @@ -1200,7 +1264,7 @@ var Screen = (function () { y: 0, fg: 7, // colors 0-15 bg: 0, - bold: false, + attrs: 0, suppress: false, // do not turn on in blink interval (for safe moving) hidden: false // do not show }; @@ -1208,35 +1272,19 @@ var Screen = (function () { var screen = []; var blinkIval; - /** Clear screen */ - function _clear() { - for (var i = W*H-1; i>=0; i--) { - var cell = screen[i]; - cell.t = ' '; - cell.bg = cursor.bg; - cell.fg = cursor.fg; - cell.bold = false; - _draw(cell); - } - } - - /** Set text and color at XY */ - function _cellAt(y, x) { - return screen[y*W+x]; - } + var frakturExceptions = { + 'C': '\u212d', + 'H': '\u210c', + 'I': '\u2111', + 'R': '\u211c', + 'Z': '\u2128', + }; /** Get cell under cursor */ function _curCell() { return screen[cursor.y*W + cursor.x]; } - /** Enable or disable cursor visibility */ - function _cursorEnable(enable) { - cursor.hidden = !enable; - cursor.a &= enable; - _draw(_curCell()); - } - /** Safely move cursor */ function cursorSet(y, x) { // Hide and prevent from showing up during the move @@ -1255,13 +1303,44 @@ var Screen = (function () { inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y; } - var e = cell.e, fg, bg; + var elem = cell.e, fg, bg, cn, t; // Colors fg = inv ? cell.bg : cell.fg; bg = inv ? cell.fg : cell.bg; // Update - e.innerText = (cell.t + ' ')[0]; - e.className = 'fg' + fg + ' bg' + bg + (cell.bold ? ' bold' : ''); + elem.textContent = t = (cell.t + ' ')[0]; + + cn = 'fg' + fg + ' bg' + bg; + if (cell.attrs & (1<<0)) cn += ' bold'; + if (cell.attrs & (1<<2)) cn += ' italic'; + if (cell.attrs & (1<<3)) cn += ' under'; + if (cell.attrs & (1<<4)) cn += ' blink'; + if (cell.attrs & (1<<5)) { + cn += ' fraktur'; + // perform substitution + if (t >= 'a' && t <= 'z') { + t = String.fromCodePoint(0x1d51e - 97 + t.charCodeAt(0)); + } + else if (t >= 'A' && t <= 'Z') { + // this set is incomplete, some exceptions are needed + if (frakturExceptions.hasOwnProperty(t)) { + t = frakturExceptions[t]; + } else { + t = String.fromCodePoint(0x1d504 - 65 + t.charCodeAt(0)); + } + } + elem.textContent = t; + } + if (cell.attrs & (1<<6)) cn += ' strike'; + + if (cell.attrs & (1<<1)) { + cn += ' faint'; + // faint requires special html - otherwise it would also dim the background. + // we use opacity on the text... + elem.innerHTML = '' + e(elem.textContent) + ''; + } + + elem.className = cn; } /** Show entire screen */ @@ -1305,6 +1384,7 @@ var Screen = (function () { t: ' ', fg: cursor.fg, bg: cursor.bg, + attrs: 0, e: e, x: i % W, y: Math.floor(i / W), @@ -1328,6 +1408,15 @@ var Screen = (function () { _draw(_curCell(), cursor.a); } }, 500); + + // blink attribute + setInterval(function () { + $('#screen').removeClass('blink-hide'); + setTimeout(function() { + $('#screen').addClass('blink-hide'); + }, 800); // 200 ms ON + }, 1000); + inited = true; } @@ -1336,11 +1425,18 @@ var Screen = (function () { return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127; } - var SEQ_SET_COLOR = 1; + /** Decode three-byte number */ + function parse3B(s, i) { + return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127; + } + + var SEQ_SET_COLOR_ATTR = 1; var SEQ_REPEAT = 2; + var SEQ_SET_COLOR = 3; + var SEQ_SET_ATTR = 4; function _load_content(str) { - var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, bold, cell; + var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell; if (!inited) _init(); @@ -1362,25 +1458,31 @@ var Screen = (function () { num = parse2B(str, i); i += 2; // fg bg bold hidden cursor.fg = num & 0x0F; cursor.bg = (num & 0xF0) >> 4; - cursor.bold = !!(num & 0x100); - cursor.hidden = !(num & 0x200); - // console.log("FG ",cursor.fg, ", BG ", cursor.bg,", BOLD ", cursor.bold, ", HIDE ", cursor.hidden); + cursor.hidden = !(num & 0x100); fg = cursor.fg; bg = cursor.bg; - bold = cursor.bold; + attrs = 0; // Here come the content while(i < str.length && ci> 4; + attrs = (num & 0xFF00)>>8; + } + else if (jc == SEQ_SET_COLOR) { num = parse2B(str, i); i += 2; fg = num & 0x0F; bg = (num & 0xF0) >> 4; - bold = !!(num & 0x100); - // console.log("Switch to ",fg,bg,bold); + } + else if (jc == SEQ_SET_ATTR) { + num = parse2B(str, i); i += 2; + attrs = num & 0xFF; } else if (jc == SEQ_REPEAT) { num = parse2B(str, i); i += 2; @@ -1390,7 +1492,7 @@ var Screen = (function () { cell.fg = fg; cell.bg = bg; cell.t = t; - cell.bold = bold; + cell.attrs = attrs; } } else { @@ -1399,7 +1501,7 @@ var Screen = (function () { t = cell.t = j; cell.fg = fg; cell.bg = bg; - cell.bold = bold; + cell.attrs = attrs; // console.log("Symbol ", j); } } @@ -1450,12 +1552,14 @@ var Conn = (function() { console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting..."); setTimeout(function() { init(); - }, 1000); + }, 200); + // this happens when the buffer gets fucked up via invalid unicode. + // we basically use polling instead of socket then } function onMessage(evt) { try { - console.log("RX: ", evt.data); + //console.log("RX: ", evt.data); // Assume all our messages are screen updates Screen.load(evt.data); } catch(e) { @@ -1530,6 +1634,7 @@ var Input = (function() { //console.log("Down ", code, e); switch(code) { case 8: sendStrMsg('\x08'); break; + case 9: sendStrMsg('\x09'); break; case 10: case 13: sendStrMsg('\x0d\x0a'); break; case 27: sendStrMsg('\x1b'); break; // this allows to directly enter control sequences diff --git a/html_orig/jssrc/appcommon.js b/html_orig/jssrc/appcommon.js index 091f806..b78747c 100644 --- a/html_orig/jssrc/appcommon.js +++ b/html_orig/jssrc/appcommon.js @@ -123,3 +123,67 @@ $.ready(function() { }, 1); } }); + + +/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */ +if (!String.fromCodePoint) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var stringFromCharCode = String.fromCharCode; + var floor = Math.floor; + var fromCodePoint = function() { + var MAX_SIZE = 0x4000; + var codeUnits = []; + var highSurrogate; + var lowSurrogate; + var index = -1; + var length = arguments.length; + if (!length) { + return ''; + } + var result = ''; + while (++index < length) { + var codePoint = Number(arguments[index]); + if ( + !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity` + codePoint < 0 || // not a valid Unicode code point + codePoint > 0x10FFFF || // not a valid Unicode code point + floor(codePoint) != codePoint // not an integer + ) { + throw RangeError('Invalid code point: ' + codePoint); + } + if (codePoint <= 0xFFFF) { // BMP code point + codeUnits.push(codePoint); + } else { // Astral code point; split in surrogate halves + // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + codePoint -= 0x10000; + highSurrogate = (codePoint >> 10) + 0xD800; + lowSurrogate = (codePoint % 0x400) + 0xDC00; + codeUnits.push(highSurrogate, lowSurrogate); + } + if (index + 1 == length || codeUnits.length > MAX_SIZE) { + result += stringFromCharCode.apply(null, codeUnits); + codeUnits.length = 0; + } + } + return result; + }; + if (defineProperty) { + defineProperty(String, 'fromCodePoint', { + 'value': fromCodePoint, + 'configurable': true, + 'writable': true + }); + } else { + String.fromCodePoint = fromCodePoint; + } + }()); +} diff --git a/html_orig/jssrc/term.js b/html_orig/jssrc/term.js index 14edd83..20bca5e 100644 --- a/html_orig/jssrc/term.js +++ b/html_orig/jssrc/term.js @@ -8,7 +8,7 @@ var Screen = (function () { y: 0, fg: 7, // colors 0-15 bg: 0, - bold: false, + attrs: 0, suppress: false, // do not turn on in blink interval (for safe moving) hidden: false // do not show }; @@ -16,35 +16,19 @@ var Screen = (function () { var screen = []; var blinkIval; - /** Clear screen */ - function _clear() { - for (var i = W*H-1; i>=0; i--) { - var cell = screen[i]; - cell.t = ' '; - cell.bg = cursor.bg; - cell.fg = cursor.fg; - cell.bold = false; - _draw(cell); - } - } - - /** Set text and color at XY */ - function _cellAt(y, x) { - return screen[y*W+x]; - } + var frakturExceptions = { + 'C': '\u212d', + 'H': '\u210c', + 'I': '\u2111', + 'R': '\u211c', + 'Z': '\u2128', + }; /** Get cell under cursor */ function _curCell() { return screen[cursor.y*W + cursor.x]; } - /** Enable or disable cursor visibility */ - function _cursorEnable(enable) { - cursor.hidden = !enable; - cursor.a &= enable; - _draw(_curCell()); - } - /** Safely move cursor */ function cursorSet(y, x) { // Hide and prevent from showing up during the move @@ -63,13 +47,44 @@ var Screen = (function () { inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y; } - var e = cell.e, fg, bg; + var elem = cell.e, fg, bg, cn, t; // Colors fg = inv ? cell.bg : cell.fg; bg = inv ? cell.fg : cell.bg; // Update - e.innerText = (cell.t + ' ')[0]; - e.className = 'fg' + fg + ' bg' + bg + (cell.bold ? ' bold' : ''); + elem.textContent = t = (cell.t + ' ')[0]; + + cn = 'fg' + fg + ' bg' + bg; + if (cell.attrs & (1<<0)) cn += ' bold'; + if (cell.attrs & (1<<2)) cn += ' italic'; + if (cell.attrs & (1<<3)) cn += ' under'; + if (cell.attrs & (1<<4)) cn += ' blink'; + if (cell.attrs & (1<<5)) { + cn += ' fraktur'; + // perform substitution + if (t >= 'a' && t <= 'z') { + t = String.fromCodePoint(0x1d51e - 97 + t.charCodeAt(0)); + } + else if (t >= 'A' && t <= 'Z') { + // this set is incomplete, some exceptions are needed + if (frakturExceptions.hasOwnProperty(t)) { + t = frakturExceptions[t]; + } else { + t = String.fromCodePoint(0x1d504 - 65 + t.charCodeAt(0)); + } + } + elem.textContent = t; + } + if (cell.attrs & (1<<6)) cn += ' strike'; + + if (cell.attrs & (1<<1)) { + cn += ' faint'; + // faint requires special html - otherwise it would also dim the background. + // we use opacity on the text... + elem.innerHTML = '' + e(elem.textContent) + ''; + } + + elem.className = cn; } /** Show entire screen */ @@ -113,6 +128,7 @@ var Screen = (function () { t: ' ', fg: cursor.fg, bg: cursor.bg, + attrs: 0, e: e, x: i % W, y: Math.floor(i / W), @@ -136,6 +152,15 @@ var Screen = (function () { _draw(_curCell(), cursor.a); } }, 500); + + // blink attribute + setInterval(function () { + $('#screen').removeClass('blink-hide'); + setTimeout(function() { + $('#screen').addClass('blink-hide'); + }, 800); // 200 ms ON + }, 1000); + inited = true; } @@ -144,11 +169,18 @@ var Screen = (function () { return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127; } - var SEQ_SET_COLOR = 1; + /** Decode three-byte number */ + function parse3B(s, i) { + return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127; + } + + var SEQ_SET_COLOR_ATTR = 1; var SEQ_REPEAT = 2; + var SEQ_SET_COLOR = 3; + var SEQ_SET_ATTR = 4; function _load_content(str) { - var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, bold, cell; + var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell; if (!inited) _init(); @@ -170,25 +202,31 @@ var Screen = (function () { num = parse2B(str, i); i += 2; // fg bg bold hidden cursor.fg = num & 0x0F; cursor.bg = (num & 0xF0) >> 4; - cursor.bold = !!(num & 0x100); - cursor.hidden = !(num & 0x200); - // console.log("FG ",cursor.fg, ", BG ", cursor.bg,", BOLD ", cursor.bold, ", HIDE ", cursor.hidden); + cursor.hidden = !(num & 0x100); fg = cursor.fg; bg = cursor.bg; - bold = cursor.bold; + attrs = 0; // Here come the content while(i < str.length && ci> 4; + attrs = (num & 0xFF00)>>8; + } + else if (jc == SEQ_SET_COLOR) { num = parse2B(str, i); i += 2; fg = num & 0x0F; bg = (num & 0xF0) >> 4; - bold = !!(num & 0x100); - // console.log("Switch to ",fg,bg,bold); + } + else if (jc == SEQ_SET_ATTR) { + num = parse2B(str, i); i += 2; + attrs = num & 0xFF; } else if (jc == SEQ_REPEAT) { num = parse2B(str, i); i += 2; @@ -198,7 +236,7 @@ var Screen = (function () { cell.fg = fg; cell.bg = bg; cell.t = t; - cell.bold = bold; + cell.attrs = attrs; } } else { @@ -207,7 +245,7 @@ var Screen = (function () { t = cell.t = j; cell.fg = fg; cell.bg = bg; - cell.bold = bold; + cell.attrs = attrs; // console.log("Symbol ", j); } } @@ -258,12 +296,14 @@ var Conn = (function() { console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting..."); setTimeout(function() { init(); - }, 1000); + }, 200); + // this happens when the buffer gets fucked up via invalid unicode. + // we basically use polling instead of socket then } function onMessage(evt) { try { - console.log("RX: ", evt.data); + //console.log("RX: ", evt.data); // Assume all our messages are screen updates Screen.load(evt.data); } catch(e) { @@ -338,6 +378,7 @@ var Input = (function() { //console.log("Down ", code, e); switch(code) { case 8: sendStrMsg('\x08'); break; + case 9: sendStrMsg('\x09'); break; case 10: case 13: sendStrMsg('\x0d\x0a'); break; case 27: sendStrMsg('\x1b'); break; // this allows to directly enter control sequences diff --git a/html_orig/sass/pages/_term.scss b/html_orig/sass/pages/_term.scss index 8152eb1..4569c9b 100755 --- a/html_orig/sass/pages/_term.scss +++ b/html_orig/sass/pages/_term.scss @@ -86,11 +86,6 @@ body.term { top: -9999px; } -// "non-bold" -.nb { - font-weight: normal !important; -} - // Tango .theme-0 { $term-colors: @@ -163,10 +158,36 @@ body.term { } } +// Attributes .bold { font-weight: bold !important; } +.faint span { // content of faint is wrapped in span + opacity: 0.6; +} + +.italic { + font-style: italic; +} + +.under { + text-decoration: underline; +} + +.strike { + text-decoration: line-through; +} + +.underline.strike { + text-decoration: underline line-through; +} + +.blink-hide .blink { + color: transparent; +} +// + .Row.color-preview { font-family: monospace; font-size: 16pt; diff --git a/user/ansi_parser.h b/user/ansi_parser.h index 1b01c7e..718556c 100644 --- a/user/ansi_parser.h +++ b/user/ansi_parser.h @@ -5,7 +5,7 @@ #include // Max nr of CSI parameters -#define CSI_N_MAX 3 +#define CSI_N_MAX 10 #define OSC_CHAR_MAX TERM_TITLE_LEN extern void apars_handle_plainchar(char c); @@ -22,7 +22,7 @@ extern void apars_reset_utf8buffer(void); // defined in the makefile #if DEBUG_ANSI #define ansi_warn warn -#define ansi_dbg warn +#define ansi_dbg dbg #else #define ansi_warn(...) #define ansi_dbg(...) diff --git a/user/ansi_parser_callbacks.c b/user/ansi_parser_callbacks.c index 3fa86f2..d034323 100644 --- a/user/ansi_parser_callbacks.c +++ b/user/ansi_parser_callbacks.c @@ -22,9 +22,17 @@ static int utf_j = 0; void ICACHE_FLASH_ATTR apars_handle_plainchar(char c) { + if (c == 7) return; // BELL - beep (TODO play beep in browser) + // collecting unicode glyphs... if (c & 0x80) { if (utf_i == 0) { + // start + if (c == 192 || c == 193 || c >= 245) { + // forbidden codes + goto fail; + } + if ((c & 0xE0) == 0xC0) { utf_i = 2; } @@ -34,15 +42,17 @@ apars_handle_plainchar(char c) else if ((c & 0xF8) == 0xF0) { utf_i = 4; } + else { + // chars over 127 that don't start unicode sequences + goto fail; + } utf_collect[0] = c; utf_j = 1; } else { if ((c & 0xC0) != 0x80) { - // bad UTF - ansi_warn("Bad UTF-8"); - apars_reset_utf8buffer(); + goto fail; } else { utf_collect[utf_j++] = c; @@ -54,9 +64,12 @@ apars_handle_plainchar(char c) } } else { - if (c == 14 || c == 15) { // pushIn, pushOut - //ansi_warn("char %d ignored", c); - // TODO implement for charset switching + if (c == 14) { + screen_set_charset_n(1); + return; + } + if (c == 15) { + screen_set_charset_n(0); return; } @@ -64,6 +77,11 @@ apars_handle_plainchar(char c) utf_collect[1] = 0; // just to make sure it's closed... screen_putchar(utf_collect); } + + return; +fail: + ansi_warn("Bad UTF-8"); + apars_reset_utf8buffer(); } void ICACHE_FLASH_ATTR @@ -77,8 +95,8 @@ apars_reset_utf8buffer(void) void ICACHE_FLASH_ATTR apars_handle_characterSet(char leadchar, char c) { - // TODO implement for charset switching -// ansi_warn("NOIMPL charset cmd %c%c", leadchar, c); + if (leadchar == '(') screen_set_charset(0, c); + else if (leadchar == ')') screen_set_charset(1, c); } void ICACHE_FLASH_ATTR @@ -97,35 +115,10 @@ apars_handle_setXCtrls(char c) void ICACHE_FLASH_ATTR apars_handle_CSI(char leadchar, int *params, int count, char keychar) { - /* - Implemented codes (from Wikipedia) - - CSI n A CUU – Cursor Up - CSI n B CUD – Cursor Down - CSI n C CUF – Cursor Forward - CSI n D CUB – Cursor Back - CSI n E CNL – Cursor Next Line - CSI n F CPL – Cursor Previous Line - CSI n G CHA – Cursor Horizontal Absolute - CSI n ; m H CUP – Cursor Position - CSI n J ED – Erase Display - CSI n K EL – Erase in Line - CSI n S SU – Scroll Up - CSI n T SD – Scroll Down - CSI n ; m f HVP – Horizontal and Vertical Position - CSI n m SGR – Select Graphic Rendition (Implemented only some) - CSI 6n DSR – Device Status Report - CSI s SCP – Save Cursor Position - CSI u RCP – Restore Cursor Position - CSI ?25l DECTCEM Hides the cursor - CSI ?25h DECTCEM Shows the cursor - - and some others - */ - int n1 = params[0]; int n2 = params[1]; -// int n3 = params[2]; + int n3 = params[2]; + static char buf[20]; // defaults switch (keychar) { @@ -306,20 +299,53 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) else if (n >= 40 && n <= 47) screen_set_bg((Color) (n - 40)); // ANSI normal bg else if (n == 39) screen_set_fg(termconf_scratch.default_fg); // default fg else if (n == 49) screen_set_bg(termconf_scratch.default_bg); // default bg - else if (n == 7) screen_inverse(true); // inverse - else if (n == 27) screen_inverse(false); // positive - else if (n == 1) screen_set_bold(true); // bold - else if (n == 21 || n == 22) screen_set_bold(false); // bold off + + else if (n == 1) screen_attr_enable(ATTR_BOLD); + else if (n == 2) screen_attr_enable(ATTR_FAINT); + else if (n == 3) screen_attr_enable(ATTR_ITALIC); + else if (n == 4) screen_attr_enable(ATTR_UNDERLINE); + else if (n == 5 || n == 6) screen_attr_enable(ATTR_BLINK); // 6 - rapid blink, not supported + else if (n == 7) screen_inverse_enable(true); + else if (n == 9) screen_attr_enable(ATTR_STRIKE); + + else if (n == 20) screen_attr_enable(ATTR_FRAKTUR); + else if (n == 21) screen_attr_disable(ATTR_BOLD); + else if (n == 22) screen_attr_disable(ATTR_FAINT); + else if (n == 23) screen_attr_disable(ATTR_ITALIC|ATTR_FRAKTUR); + else if (n == 24) screen_attr_disable(ATTR_UNDERLINE); + else if (n == 25) screen_attr_disable(ATTR_BLINK); + else if (n == 27) screen_inverse_enable(false); + else if (n == 29) screen_attr_disable(ATTR_STRIKE); + else if (n >= 90 && n <= 97) screen_set_fg((Color) (n - 90 + 8)); // AIX bright fg else if (n >= 100 && n <= 107) screen_set_bg((Color) (n - 100 + 8)); // AIX bright bg else { - ansi_warn("NOIMPL SGR attr %d", n); + ansi_warn("NOIMPL SGR attr %d", n); } } break; - case 't': // SunView code to set screen size (from GNU Screen) - screen_resize(n1, n2); + case 't': // xterm hacks + switch(n1) { + case 8: // set size + screen_resize(n2, n3); + break; + case 18: // report size + printf(buf, "\033[8;%d;%dt", termconf_scratch.height, termconf_scratch.width); + UART_WriteString(UART0, buf, UART_TIMEOUT_US); + break; + case 11: // Report iconified -> is not iconified + UART_WriteString(UART0, "\033[1t", UART_TIMEOUT_US); + break; + case 21: // Report title + UART_WriteString(UART0, "\033]L", UART_TIMEOUT_US); + UART_WriteString(UART0, termconf_scratch.title, UART_TIMEOUT_US); + UART_WriteString(UART0, "\033\\", UART_TIMEOUT_US); + break; + case 24: // Set Height only + screen_resize(n2, termconf_scratch.width); + break; + } break; case 'L': @@ -340,7 +366,7 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) case 'r': // TODO scrolling region - ansi_warn("NOIMPL scrolling region"); +// ansi_warn("NOIMPL scrolling region"); break; case 'g': @@ -358,6 +384,18 @@ apars_handle_CSI(char leadchar, int *params, int count, char keychar) ansi_warn("NOIMPL CSI setmode %d", n1); break; + case 'p': + if (leadchar == '!') { + info("SOFT RESET!"); + system_restart(); + } + break; + + case 'c': + // report capabilities (pretend we're vt4xx) + UART_WriteString(UART0, "\033[?64;22;c", UART_TIMEOUT_US); + break; + default: ansi_warn("Unknown CSI: %c", keychar); apars_handle_badseq(); diff --git a/user/screen.c b/user/screen.c index 74ebc00..ec08fab 100644 --- a/user/screen.c +++ b/user/screen.c @@ -8,6 +8,9 @@ TerminalConfigBundle * const termconf = &persist.current.termconf; TerminalConfigBundle termconf_scratch; +// forward declare +static void utf8_remap(char* out, char g, char table); + #define W termconf_scratch.width #define H termconf_scratch.height @@ -61,7 +64,7 @@ typedef struct __attribute__((packed)){ char c[4]; // space for a full unicode character Color fg : 4; Color bg : 4; - bool bold : 1; + u8 attrs; } Cell; /** @@ -75,10 +78,15 @@ static Cell screen[MAX_SCREEN_SIZE]; static struct { int x; //!< X coordinate int y; //!< Y coordinate - bool visible; //!< Visible - bool inverse; //!< Inverse colors bool autowrap; //!< Wrapping when EOL - bool bold; //!< Bold style + bool visible; //!< Visible (not attribute, DEC special) + bool inverse; + u8 attrs; + + char charset0; + char charset1; + int charsetN; + Color fg; //!< Foreground color for writing Color bg; //!< Background color for writing } cursor; @@ -89,10 +97,10 @@ static struct { static struct { int x; int y; - - // optionally saved attrs + // mark that attrs are saved bool withAttrs; - bool inverse; + u8 attrs; + bool inverse; // attribute that's not in the bitfield Color fg; Color bg; } cursor_sav; @@ -120,8 +128,8 @@ static inline void clear_range(unsigned int from, unsigned int to) { if (to >= W*H) to = W*H-1; - Color fg = cursor.inverse ? cursor.bg : cursor.fg; - Color bg = cursor.inverse ? cursor.fg : cursor.bg; + Color fg = (cursor.inverse) ? cursor.bg : cursor.fg; + Color bg = (cursor.inverse) ? cursor.fg : cursor.bg; Cell sample; sample.c[0] = ' '; @@ -130,7 +138,7 @@ clear_range(unsigned int from, unsigned int to) sample.c[3] = 0; sample.fg = fg; sample.bg = bg; - sample.bold = false; + sample.attrs = 0; for (unsigned int i = from; i <= to; i++) { memcpy(&screen[i], &sample, sizeof(Cell)); @@ -148,9 +156,12 @@ cursor_reset(void) cursor.fg = termconf_scratch.default_fg; cursor.bg = termconf_scratch.default_bg; cursor.visible = 1; - cursor.inverse = 0; cursor.autowrap = 1; - cursor.bold = 0; + cursor.attrs = 0; + + cursor.charset0 = 'B'; + cursor.charset1 = '0'; + cursor.charsetN = 0; } //endregion @@ -188,8 +199,8 @@ screen_reset_cursor(void) { cursor.fg = termconf_scratch.default_fg; cursor.bg = termconf_scratch.default_bg; - cursor.inverse = 0; - cursor.bold = 0; + cursor.attrs = 0; + cursor.inverse = false; } /** @@ -262,7 +273,7 @@ screen_fill_with_E(void) sample.c[3] = 0; sample.fg = termconf_scratch.default_fg; sample.bg = termconf_scratch.default_bg; - sample.bold = false; + sample.attrs = 0; for (unsigned int i = 0; i <= W*H-1; i++) { memcpy(&screen[i], &sample, sizeof(Cell)); @@ -531,11 +542,11 @@ screen_cursor_save(bool withAttrs) if (withAttrs) { cursor_sav.fg = cursor.fg; cursor_sav.bg = cursor.bg; - cursor_sav.inverse = cursor.inverse; + cursor_sav.attrs = cursor.attrs; } else { cursor_sav.fg = termconf_scratch.default_fg; cursor_sav.bg = termconf_scratch.default_bg; - cursor_sav.inverse = 0; + cursor_sav.attrs = 0; // avoid leftovers if the wrong restore is used } } @@ -552,7 +563,7 @@ screen_cursor_restore(bool withAttrs) if (withAttrs) { cursor.fg = cursor_sav.fg; cursor.bg = cursor_sav.bg; - cursor.inverse = cursor_sav.inverse; + cursor.attrs = cursor_sav.attrs; } NOTIFY_DONE(); @@ -604,43 +615,19 @@ screen_set_bg(Color color) cursor.bg = color; } -/** - * Set cursor foreground and background color - */ -void ICACHE_FLASH_ATTR -screen_set_colors(Color fg, Color bg) +void screen_attr_enable(u8 attrs) { - screen_set_fg(fg); - screen_set_bg(bg); + cursor.attrs |= attrs; } -/** - * Invert colors - */ -void ICACHE_FLASH_ATTR -screen_inverse(bool inverse) +void screen_attr_disable(u8 attrs) { - cursor.inverse = inverse; + cursor.attrs &= ~attrs; } -/** - * Make foreground bright. - * - * This relates to the '1' SGR command which originally means - * "bold font". We interpret that as "Bright", similar to other - * terminal emulators. - * - * Note that the bright colors can be used without bold using the 90+ codes - */ -void ICACHE_FLASH_ATTR -screen_set_bold(bool bold) +void screen_inverse_enable(bool ena) { - if (!bold) { - cursor.fg = (Color) (cursor.fg % 8); - } else { - cursor.fg = (Color) ((cursor.fg % 8) + 8); // change anything to the bright colors - } - cursor.bold = bold; + cursor.inverse = ena; } //endregion @@ -657,12 +644,26 @@ bool ICACHE_FLASH_ATTR screen_isCoordValid(int y, int x) return x >= 0 && y >= 0 && x < W && y < H; } + +void ICACHE_FLASH_ATTR screen_set_charset_n(int Gx) +{ + if (Gx < 0 || Gx > 1) return; // bad n + cursor.charsetN = Gx; +} + +void ICACHE_FLASH_ATTR screen_set_charset(int Gx, char charset) +{ + if (Gx == 0) cursor.charset0 = charset; + if (Gx == 1) cursor.charset1 = charset; +} + /** * Set a character in the cursor color, move to right with wrap. */ void ICACHE_FLASH_ATTR screen_putchar(const char *ch) { + char buf[4]; NOTIFY_LOCK(); Cell *c = &screen[cursor.x + cursor.y * W]; @@ -687,15 +688,11 @@ screen_putchar(const char *ch) cursor.y--; } } - // erase target cell - c = &screen[cursor.x + cursor.y * W]; - c->c[0] = ' '; - c->c[1] = 0; - c->c[2] = 0; - c->c[3] = 0; + // apparently backspace should not clear the cell goto done; case 9: // TAB + // TODO change if tab setting is ever implemented if (cursor.x<((W-1)-(W-1)%4)) { c->c[0] = ' '; c->c[1] = 0; @@ -715,8 +712,14 @@ screen_putchar(const char *ch) } } - // copy unicode char - strncpy(c->c, ch, 4); + if (ch[1] == 0 && ch[0] <= 0x7f) { + // we have len=1 and ASCII + utf8_remap(c->c, ch[0], (cursor.charsetN == 0) ? cursor.charset0 : cursor.charset1); + } + else { + // copy unicode char + strncpy(c->c, ch, 4); + } if (cursor.inverse) { c->fg = cursor.bg; @@ -725,7 +728,7 @@ screen_putchar(const char *ch) c->fg = cursor.fg; c->bg = cursor.bg; } - c->bold = cursor.bold; + c->attrs = cursor.attrs; cursor.x++; // X wrap @@ -748,6 +751,84 @@ done: NOTIFY_DONE(); } +/** + * translates VT100 ACS escape codes to Unicode values. + * Based on rxvt-unicode screen.C table. + */ +static const u16 vt100_to_unicode[62] = +{ +// ? ? ? ? ? ? ? +// A=UPARR B=DNARR C=RTARR D=LFARR E=FLBLK F=3/4BL G=SNOMN + 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, +// H= I= J= K= L= M= N= + 0, 0, 0, 0, 0, 0, 0, +// O= P= Q= R= S= T= U= + 0, 0, 0, 0, 0, 0, 0, +// V= W= X= Y= Z= [= \= + 0, 0, 0, 0, 0, 0, 0, +// ? ? v->0 v->1 v->2 v->3 v->4 +// ]= ^= _=SPC `=DIAMN a=HSMED b=HT c=FF + 0, 0, 0x0020, 0x25c6, 0x2592, 0x2409, 0x240c, +// v->5 v->6 v->7 v->8 v->9 v->a v->b +// d=CR e=LF f=DEGRE g=PLSMN h=NL i=VT j=SL-BR + 0x240d, 0x240a, 0x00b0, 0x00b1, 0x2424, 0x240b, 0x2518, +// v->c v->d v->e v->f v->10 v->11 v->12 +// k=SL-TR l=SL-TL m=SL-BL n=SL-+ o=SL-T1 p=SL-T2 q=SL-HZ + 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, 0x23bb, 0x2500, +// v->13 v->14 v->15 v->16 v->17 v->18 v->19 +// r=SL-T4 s=SL-T5 t=SL-VR u=SL-VL v=SL-HU w=Sl-HD x=SL-VT + 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, 0x2502, +// v->1a v->1b b->1c v->1d v->1e/a3 v->1f +// y=LT-EQ z=GT-EQ {=PI |=NOTEQ }=POUND ~=DOT + 0x2264, 0x2265, 0x03c0, 0x2260, 0x20a4, 0x00b7 +}; + +/** + * UTF remap + * @param out - output char[4] + * @param g - ASCII char + * @param table - table name (0, A, B) + */ +static void ICACHE_FLASH_ATTR +utf8_remap(char *out, char g, char table) +{ + u16 utf = 0; + + switch (table) + { + case '0': /* DEC Special Character & Line Drawing Set */ + if ((g >= 0x41) && (g <= 0x7e) && (vt100_to_unicode[g - 0x41])) { + utf = vt100_to_unicode[g - 0x41]; + } + break; + case 'A': /* UK, replaces # with GBP */ + if (g == '#') utf = 0x20a4; + break; + } + + if (utf > 0x7F) { + // formulas taken from: https://gist.github.com/yamamushi/5823402 + if ((utf >= 0x80) && (utf <= 0x07FF)) { + out[0] = (char) ((utf >> 0x06) ^ 0xC0); + out[1] = (char) (((utf ^ 0xFFC0) | 0x80) & ~0x40); + out[2]=0; + } + else if ((utf >= 0x0800) && (utf <= 0xFFFF)) { + out[0] = (char) (((utf ^ 0xFC0FFF) >> 0x0C) | 0xE0); + out[1] = (char) ((((utf ^ 0xFFF03F) >> 0x06) | 0x80) & ~0x40); + out[2] = (char) (((utf ^ 0xFFFC0) | 0x80) & ~0x40); + out[3]=0; + } else { + out[0] = g; + out[1] = 0; + } + } else { + out[0] = g; + out[1] = 0; + } +} + + //region Serialization @@ -789,7 +870,7 @@ void screen_dd(void) struct ScreenSerializeState { Color lastFg; Color lastBg; - bool lastBold; + bool lastAttrs; char lastChar[4]; int index; }; @@ -798,8 +879,24 @@ void ICACHE_FLASH_ATTR encode2B(u16 number, WordB2 *stru) { stru->lsb = (u8) (number % 127); - stru->msb = (u8) ((number - stru->lsb) / 127 + 1); + number = (u16) ((number - stru->lsb) / 127); + stru->lsb += 1; + + stru->msb = (u8) (number + 1); +} + +void ICACHE_FLASH_ATTR +encode3B(u32 number, WordB3 *stru) +{ + stru->lsb = (u8) (number % 127); + number = (number - stru->lsb) / 127; stru->lsb += 1; + + stru->msb = (u8) (number % 127); + number = (number - stru->msb) / 127; + stru->msb += 1; + + stru->xsb = (u8) (number + 1); } /** @@ -845,6 +942,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) Cell *cell, *cell0; WordB2 w1, w2, w3, w4, w5; + WordB3 lw1; size_t remain = buf_len; int used = 0; char *bb = buffer; @@ -860,7 +958,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) ss->index = 0; ss->lastBg = 0; ss->lastFg = 0; - ss->lastBold = false; + ss->lastAttrs = 0; memset(ss->lastChar, 0, 4); // this ensures the first char is never "repeat" encode2B((u16) H, &w1); @@ -870,8 +968,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) encode2B((u16) ( cursor.fg | (cursor.bg<<4) | - (cursor.bold?0x100:0) | - (cursor.visible?0x200:0)) + (cursor.visible ? 1<<8 : 0)) , &w5); // H W X Y Attribs @@ -887,7 +984,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) while (i < W*H && cell->fg == ss->lastFg && cell->bg == ss->lastBg - && cell->bold == ss->lastBold + && cell->attrs == ss->lastAttrs && strneq(cell->c, ss->lastChar, 4)) { // Repeat repCnt++; @@ -896,13 +993,25 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) if (repCnt == 0) { // No repeat - if (cell0->bold != ss->lastBold || cell0->fg != ss->lastFg || cell0->bg != ss->lastBg) { - encode2B((u16) ( + bool changeAttrs = cell0->attrs != ss->lastAttrs; + bool changeColors = (cell0->fg != ss->lastFg || cell0->bg != ss->lastBg); + if (!changeAttrs && changeColors) { + encode2B(cell0->fg | (cell0->bg<<4), &w1); + bufprint("\x03%c%c", w1.lsb, w1.msb); + } + else if (changeAttrs && !changeColors) { + // attrs only + encode2B(cell0->attrs, &w1); + bufprint("\x04%c%c", w1.lsb, w1.msb); + } + else if (changeAttrs && changeColors) { + // colors and attrs + encode3B((u32) ( cell0->fg | (cell0->bg<<4) | - (cell0->bold?0x100:0)) - , &w1); - bufprint("\x01%c%c", w1.lsb, w1.msb); + (cell0->attrs<<8)) + , &lw1); + bufprint("\x01%c%c%c", lw1.lsb, lw1.msb, lw1.xsb); } // copy the symbol, until first 0 or reached 4 bytes @@ -914,7 +1023,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) ss->lastFg = cell0->fg; ss->lastBg = cell0->bg; - ss->lastBold = cell0->bold; + ss->lastAttrs = cell0->attrs; memcpy(ss->lastChar, cell0->c, 4); i++; diff --git a/user/screen.h b/user/screen.h index 705b4bf..4920d62 100644 --- a/user/screen.h +++ b/user/screen.h @@ -46,6 +46,14 @@ typedef enum { CHANGE_LABELS, } ScreenNotifyChangeTopic; +#define ATTR_BOLD (1<<0) +#define ATTR_FAINT (1<<1) +#define ATTR_ITALIC (1<<2) +#define ATTR_UNDERLINE (1<<3) +#define ATTR_BLINK (1<<4) +#define ATTR_FRAKTUR (1<<5) +#define ATTR_STRIKE (1<<6) + #define SCREEN_NOTIFY_DELAY_MS 20 typedef struct { @@ -95,6 +103,12 @@ typedef struct { u8 msb; } WordB2; +typedef struct { + u8 lsb; + u8 msb; + u8 xsb; +} WordB3; + /** Encode number to two nice ASCII bytes */ void encode2B(u16 number, WordB2 *stru); @@ -119,7 +133,7 @@ void screen_clear_in_line(unsigned int count); void screen_scroll_up(unsigned int lines); /** Shift screen downwards */ void screen_scroll_down(unsigned int lines); -/** esc # 8 - fill entire screen with E of default colors */ +/** esc # 8 - fill entire screen with E of default colors (DEC alignment display) */ void screen_fill_with_E(void); // --- insert / delete --- @@ -157,12 +171,14 @@ void screen_wrap_enable(bool enable); void screen_set_fg(Color color); /** Set cursor background coloor */ void screen_set_bg(Color color); -/** make foreground bright */ -void screen_set_bold(bool bold); -/** Set cursor foreground and background color */ -void screen_set_colors(Color fg, Color bg); -/** Invert colors */ -void screen_inverse(bool inverse); + +// enable or disable attrs by bitmask +void screen_attr_enable(u8 attrs); +void screen_attr_disable(u8 attrs); +void screen_inverse_enable(bool ena); + +void screen_set_charset_n(int Gx); +void screen_set_charset(int Gx, char charset); /** * Set a character in the cursor color, move to right with wrap.