From afa34a43de013987f9765b409f97f66972c05512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Mon, 23 Jan 2017 18:27:51 +0100 Subject: [PATCH] working terminal --- Makefile | 10 ++- build_parser.sh | 2 + build_web.sh | 4 +- html/script.js | 2 +- html_orig/script.js | 24 ++++-- html_orig/{index.html => term.html} | 0 user/ansi_parser.c | 71 ++++++++------- user/ansi_parser.rl | 6 +- user/screen.c | 128 +++++++++++++++++++++++----- user/screen.h | 2 +- user/serial.c | 9 +- user/user_main.c | 9 +- 12 files changed, 197 insertions(+), 70 deletions(-) rename html_orig/{index.html => term.html} (100%) diff --git a/Makefile b/Makefile index c8883f9..2f2653a 100644 --- a/Makefile +++ b/Makefile @@ -182,9 +182,15 @@ $1/%.o: %.S $(Q) $(CC) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ endef -.PHONY: all checkdirs clean libesphttpd default-tgt +.PHONY: all web parser checkdirs clean libesphttpd default-tgt -all: checkdirs $(TARGET_OUT) $(FW_BASE) +web: + $(Q) ./build_web.sh + +parser: + $(Q) ./build_parser.sh + +all: checkdirs web parser $(TARGET_OUT) $(FW_BASE) libesphttpd/Makefile: $(Q) [[ -e "libesphttpd/Makefile" ]] || echo -e "\e[31mlibesphttpd submodule missing.\nIf build fails, run \"git submodule init\" and \"git submodule update\".\e[0m" diff --git a/build_parser.sh b/build_parser.sh index fd0a7be..3f2417a 100755 --- a/build_parser.sh +++ b/build_parser.sh @@ -1,3 +1,5 @@ #!/bin/bash +echo "-- Building parser from Ragel source --" + ragel -L -G0 user/ansi_parser.rl -o user/ansi_parser.c diff --git a/build_web.sh b/build_web.sh index b15671a..71953b6 100755 --- a/build_web.sh +++ b/build_web.sh @@ -1,6 +1,8 @@ #!/bin/bash +echo "-- Preparing WWW files --" + mkdir -p html yuicompressor html_orig/style.css > html/style.css yuicompressor html_orig/script.js > html/script.js -minify --type=html html_orig/index.html -o html/term.tpl +minify --type=html html_orig/term.html -o html/term.tpl diff --git a/html/script.js b/html/script.js index c01760d..9ac02fc 100644 --- a/html/script.js +++ b/html/script.js @@ -1 +1 @@ -var $=function(d,c){d=d.match(/^(\W)?(.*)/);return(c||document)["getElement"+(d[1]?d[1]=="#"?"ById":"sByClassName":"sByTagName")](d[2])};var m=function(e,d,f){d=document;f=d.createElement("p");f.innerHTML=e;e=d.createDocumentFragment();while(d=f.firstChild){e.appendChild(d)}return e};(function(){function a(r){return document.createElement(r)}var b=26,k=10;var l={a:false,x:0,y:0,suppress:false,hidden:false};var o=[];var g=["#111213","#CC0000","#4E9A06","#C4A000","#3465A4","#75507B","#06989A","#D3D7CF","#555753","#EF2929","#8AE234","#FCE94F","#729FCF","#AD7FA8","#34E2E2","#EEEEEC"];function p(){o.forEach(function(r,s){r.t=" ";r.fg=7;r.bg=0;d(r)})}function c(s,r){return o[s*b+r]}function i(){return c(l.y,l.x)}function q(r){l.hidden=!r;l.a&=r;d(i(),l.a)}function f(s,r){l.suppress=true;d(i(),false);l.x=r;l.y=s;l.suppress=false;d(i(),l.a)}function d(s,r){var v=s.e,t,u;t=r?s.bg:s.fg;u=r?s.fg:s.bg;v.innerText=(s.t+" ")[0];v.style.color=e(t);v.style.backgroundColor=e(u);v.style.fontWeight=t>7?"bold":"normal"}function h(){o.forEach(function(s,t){var r=l.a&&(t==l.y*b+l.x);d(s,r)})}function j(u){l.x=u.x;l.y=u.y;var v=0,B=0,x=u.screen;var r,w,A,z,s,y;while(v0){y=parseInt(x.substr(v+1,s));v=v+s+1;for(;y>0&&B15){r=0}return g[r]}function n(v){var u,r,t=$("#screen");for(var s=0;s0)&&(s%b==0)){t.appendChild(a("br"))}t.appendChild(u);r={t:" ",fg:7,bg:0,e:u};o.push(r);d(r)}setInterval(function(){l.a=!l.a;if(l.hidden){l.a=false}if(!l.suppress){d(i(),l.a)}},500);j(v)}window.Term={init:n,load:j,setCursor:f,enableCursor:q,clear:p}})();(function(){var g="ws://"+window.location.host+"/ws/update.cgi";var d;function c(i){console.log("CONNECTED")}function b(i){console.error("SOCKET CLOSED")}function f(i){try{Term.load(JSON.parse(i.data))}catch(j){console.error(j)}}function e(i){console.error(i.data)}function a(i){if(typeof i!="string"){i=JSON.stringify(i)}d.send(i)}function h(){d=new WebSocket(g);d.onopen=c;d.onclose=b;d.onmessage=f;d.onerror=e;console.log("Opening socket.")}window.Conn={ws:null,init:h,send:a}})();function init(a){Term.init(a);Conn.init()}; \ No newline at end of file +var $=function(d,c){d=d.match(/^(\W)?(.*)/);return(c||document)["getElement"+(d[1]?d[1]=="#"?"ById":"sByClassName":"sByTagName")](d[2])};var m=function(e,d,f){d=document;f=d.createElement("p");f.innerHTML=e;e=d.createDocumentFragment();while(d=f.firstChild){e.appendChild(d)}return e};(function(){function a(r){return document.createElement(r)}var b=26,k=10;var l={a:false,x:0,y:0,suppress:false,hidden:false};var o=[];var g=["#111213","#CC0000","#4E9A06","#C4A000","#3465A4","#75507B","#06989A","#D3D7CF","#555753","#EF2929","#8AE234","#FCE94F","#729FCF","#AD7FA8","#34E2E2","#EEEEEC"];function p(){o.forEach(function(r,s){r.t=" ";r.fg=7;r.bg=0;d(r)})}function c(s,r){return o[s*b+r]}function i(){return c(l.y,l.x)}function q(r){l.hidden=!r;l.a&=r;d(i(),l.a)}function f(s,r){l.suppress=true;d(i(),false);l.x=r;l.y=s;l.suppress=false;d(i(),l.a)}function d(s,r){var v=s.e,t,u;t=r?s.bg:s.fg;u=r?s.fg:s.bg;v.innerText=(s.t+" ")[0];v.style.color=e(t);v.style.backgroundColor=e(u);v.style.fontWeight=t>7?"bold":"normal"}function h(){o.forEach(function(s,t){var r=l.a&&(t==l.y*b+l.x);d(s,r)})}function j(v){l.x=v.x;l.y=v.y;var w=0,C=0,y=v.screen;var r=7,x=0;while(w0){var z=parseInt(y.substr(w+1,s));w=w+s+1;for(;z>0&&C15){r=0}return g[r]}function n(v){var u,r,t=$("#screen");for(var s=0;s0)&&(s%b==0)){t.appendChild(a("br"))}t.appendChild(u);r={t:" ",fg:7,bg:0,e:u};o.push(r);d(r)}setInterval(function(){l.a=!l.a;if(l.hidden){l.a=false}if(!l.suppress){d(i(),l.a)}},500);j(v)}window.Term={init:n,load:j,setCursor:f,enableCursor:q,clear:p}})();(function(){var g="ws://"+window.location.host+"/ws/update.cgi";var d;function c(i){console.log("CONNECTED")}function b(i){console.error("SOCKET CLOSED")}function f(i){try{console.log("RX: ",i.data);Term.load(JSON.parse(i.data))}catch(j){console.error(j)}}function e(i){console.error(i.data)}function a(i){if(typeof i!="string"){i=JSON.stringify(i)}d.send(i)}function h(){d=new WebSocket(g);d.onopen=c;d.onclose=b;d.onmessage=f;d.onerror=e;console.log("Opening socket.")}window.Conn={ws:null,init:h,send:a}})();function init(a){Term.init(a);Conn.init()}; \ No newline at end of file diff --git a/html_orig/script.js b/html_orig/script.js index ce44c25..9d0548c 100644 --- a/html_orig/script.js +++ b/html_orig/script.js @@ -163,15 +163,26 @@ var m = function( cursor.y = obj.y; // Simple compression - hexFG hexBG 'ASCII' (r/s/t/u NUM{1,2,3,4})? + // comma instead of both colors = same as before var i = 0, ci = 0, str = obj.screen; - var fg, bg, t, cell, repchars, rep; + var fg = 7, bg = 0; while(i < str.length && ci 0) { - rep = parseInt(str.substr(i+1,repchars)); + var rep = parseInt(str.substr(i+1,repchars)); i = i + repchars + 1; for (; rep>0 && ci 0) notifyLock--; \ + if (notifyLock == 0) screen_notifyChange(); \ + } while(0) + /** * Reset a cell */ @@ -103,12 +115,13 @@ cursor_reset(void) void ICACHE_FLASH_ATTR screen_init(void) { + NOTIFY_LOCK(); for (unsigned int i = 0; i < MAX_SCREEN_SIZE; i++) { cell_init(&screen[i]); } cursor_reset(); - screen_notifyChange(); + NOTIFY_DONE(); } /** @@ -117,9 +130,10 @@ screen_init(void) void ICACHE_FLASH_ATTR screen_reset(void) { + NOTIFY_LOCK(); screen_clear(CLEAR_ALL); cursor_reset(); - screen_notifyChange(); + NOTIFY_DONE(); } /** @@ -128,6 +142,7 @@ screen_reset(void) void ICACHE_FLASH_ATTR screen_clear(ClearMode mode) { + NOTIFY_LOCK(); switch (mode) { case CLEAR_ALL: clear_range(0, W * H - 1); @@ -141,7 +156,7 @@ screen_clear(ClearMode mode) clear_range(0, (cursor.y * W) + cursor.x); break; } - screen_notifyChange(); + NOTIFY_DONE(); } /** @@ -150,6 +165,7 @@ screen_clear(ClearMode mode) void ICACHE_FLASH_ATTR screen_clear_line(ClearMode mode) { + NOTIFY_LOCK(); switch (mode) { case CLEAR_ALL: clear_range(cursor.y * W, (cursor.y + 1) * W - 1); @@ -163,7 +179,7 @@ screen_clear_line(ClearMode mode) clear_range(cursor.y * W, cursor.y * W + cursor.x); break; } - screen_notifyChange(); + NOTIFY_DONE(); } //endregion @@ -179,6 +195,7 @@ screen_clear_line(ClearMode mode) void ICACHE_FLASH_ATTR screen_resize(Coordinate w, Coordinate h) { + NOTIFY_LOCK(); // sanitize if (w < 1) w = 1; if (h < 1) h = 1; @@ -186,7 +203,7 @@ screen_resize(Coordinate w, Coordinate h) W = w; H = h; screen_reset(); - screen_notifyChange(); + NOTIFY_DONE(); } /** @@ -195,13 +212,15 @@ screen_resize(Coordinate w, Coordinate h) void ICACHE_FLASH_ATTR screen_scroll_up(unsigned int lines) { + NOTIFY_LOCK(); if (lines >= H - 1) { screen_clear(CLEAR_ALL); - return; + goto done; } + // bad cmd if (lines == 0) { - return; + goto done; } int y; @@ -210,7 +229,9 @@ screen_scroll_up(unsigned int lines) } clear_range(y * W, W * H - 1); - screen_notifyChange(); + +done: + NOTIFY_DONE(); } /** @@ -219,13 +240,15 @@ screen_scroll_up(unsigned int lines) void ICACHE_FLASH_ATTR screen_scroll_down(unsigned int lines) { + NOTIFY_LOCK(); if (lines >= H - 1) { screen_clear(CLEAR_ALL); - return; + goto done; } + // bad cmd if (lines == 0) { - return; + goto done; } int y; @@ -234,7 +257,8 @@ screen_scroll_down(unsigned int lines) } clear_range(0, lines * W-1); - screen_notifyChange(); +done: + NOTIFY_DONE(); } //endregion @@ -247,11 +271,12 @@ screen_scroll_down(unsigned int lines) void ICACHE_FLASH_ATTR screen_cursor_set(Coordinate x, Coordinate y) { + NOTIFY_LOCK(); if (x >= W) x = W - 1; if (y >= H) y = H - 1; cursor.x = x; cursor.y = y; - screen_notifyChange(); + NOTIFY_DONE(); } /** @@ -260,9 +285,10 @@ screen_cursor_set(Coordinate x, Coordinate y) void ICACHE_FLASH_ATTR screen_cursor_set_x(Coordinate x) { + NOTIFY_LOCK(); if (x >= W) x = W - 1; cursor.x = x; - screen_notifyChange(); + NOTIFY_DONE(); } /** @@ -271,9 +297,10 @@ screen_cursor_set_x(Coordinate x) void ICACHE_FLASH_ATTR screen_cursor_set_y(Coordinate y) { + NOTIFY_LOCK(); if (y >= H) y = H - 1; cursor.y = y; - screen_notifyChange(); + NOTIFY_DONE(); } /** @@ -282,10 +309,27 @@ screen_cursor_set_y(Coordinate y) void ICACHE_FLASH_ATTR screen_cursor_move(int dx, int dy) { - if (dx < 0 && -dx > cursor.x) dx = -cursor.x; - if (dy < 0 && -dy > cursor.y) dy = -cursor.y; - screen_cursor_set(cursor.x + dx, cursor.y + dy); - screen_notifyChange(); + NOTIFY_LOCK(); + int move; + + cursor.x += dx; + cursor.y += dy; + if (cursor.x >= W) cursor.x = W - 1; + if (cursor.x < 0) cursor.x = 0; + + if (cursor.y < 0) { + move = -cursor.y; + cursor.y = 0; + screen_scroll_down((unsigned int)move); + } + + if (cursor.y >= H) { + move = cursor.y - (H - 1); + cursor.y = H - 1; + screen_scroll_up((unsigned int)move); + } + + NOTIFY_DONE(); } /** @@ -304,9 +348,10 @@ screen_cursor_save(void) void ICACHE_FLASH_ATTR screen_cursor_restore(void) { + NOTIFY_LOCK(); cursor.x = cursor_sav.x; cursor.y = cursor_sav.y; - screen_notifyChange(); + NOTIFY_DONE(); } /** @@ -315,8 +360,9 @@ screen_cursor_restore(void) void ICACHE_FLASH_ATTR screen_cursor_enable(bool enable) { + NOTIFY_LOCK(); cursor.visible = enable; - screen_notifyChange(); + NOTIFY_DONE(); } //endregion @@ -383,7 +429,44 @@ screen_set_bright_fg(void) void ICACHE_FLASH_ATTR screen_putchar(char ch) { + NOTIFY_LOCK(); + Cell *c = &screen[cursor.x + cursor.y * W]; + + // Special treatment for CRLF + switch (ch) { + case '\r': + screen_cursor_set_x(0); + goto done; + + case '\n': + screen_cursor_move(0, 1); + goto done; + + case 8: // BS + if (cursor.x > 0) cursor.x--; + // erase target cell + c = &screen[cursor.x + cursor.y * W]; + c->c = ' '; + goto done; + + case 9: // TAB + c->c = ' '; + // nested recurs >:( but it's ok + screen_putchar(' '); + screen_putchar(' '); + screen_putchar(' '); + screen_putchar(' '); + goto done; + + default: + if (ch < ' ') { + // Discard + warn("Ignoring control char %d", (int)c); + goto done; + } + } + c->c = ch; if (cursor.inverse) { @@ -407,7 +490,8 @@ screen_putchar(char ch) } } - screen_notifyChange(); +done: + NOTIFY_DONE(); } @@ -495,7 +579,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) ss->lastFg = 0; ss->lastChar = '\0'; - bufprint("{x:%d,y:%d,screen:\"", cursor.x, cursor.y); + bufprint("{\"x\":%d,\"y\":%d,\"screen\":\"", cursor.x, cursor.y); } int i = ss->index; diff --git a/user/screen.h b/user/screen.h index 4f0da26..677b6c4 100644 --- a/user/screen.h +++ b/user/screen.h @@ -53,7 +53,7 @@ typedef enum { } ClearMode; typedef uint8_t Color; -typedef unsigned int Coordinate; +typedef int Coordinate; httpd_cgi_state ICACHE_FLASH_ATTR screenSerializeToBuffer(char *buffer, size_t buf_len, void **data); diff --git a/user/serial.c b/user/serial.c index 99c9f10..6b63602 100644 --- a/user/serial.c +++ b/user/serial.c @@ -25,7 +25,10 @@ void ICACHE_FLASH_ATTR serialInit(void) */ void ICACHE_FLASH_ATTR UART_HandleRxByte(char c) { - // TODO buffering, do not run parser after just 1 char - printf("(%c)", c); - ansi_parser(&c, 1); + if (c > 0 && c < 127) { + // TODO buffering, do not run parser after just 1 char + ansi_parser(&c, 1); + } else { + warn("Bad char %d ('%c')", (unsigned char)c, c); + } } \ No newline at end of file diff --git a/user/user_main.c b/user/user_main.c index 6b2b4a3..b161788 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -55,8 +55,7 @@ void ICACHE_FLASH_ATTR myWebsocketConnect(Websock *ws) { * @return */ httpd_cgi_state ICACHE_FLASH_ATTR tplScreen(HttpdConnData *connData, char *token, void **arg) { - // cleanup - if (!connData) { + if (token==NULL) { // Release data object screenSerializeToBuffer(NULL, 0, arg); return HTTPD_CGI_DONE; @@ -67,6 +66,9 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplScreen(HttpdConnData *connData, char *token if (streq(token, "screenData")) { httpd_cgi_state cont = screenSerializeToBuffer(buff, bufsiz, arg); + + dbg("Sending buf: %s", buff); + httpdSend(connData, buff, -1); return cont; } @@ -104,8 +106,7 @@ HttpdBuiltInUrl builtInUrls[]={ //ICACHE_RODATA_ATTR // TODO add funcs for WiFi management (when web UI is added) -// ROUTE_TPL_FILE("/", tplScreen, "term.tpl"), - ROUTE_TPL("/term.tpl", tplScreen), + ROUTE_TPL_FILE("/", tplScreen, "term.tpl"), ROUTE_FILESYSTEM(), ROUTE_END(), };