w.i.p. Terminal Themes added, improved terminal color picker, new compression in front-end

pull/111/merge
Ondřej Hruška 7 years ago
parent 76167b8014
commit d711909812
  1. 13
      html_orig/_debug_replacements.php
  2. 547
      html_orig/css/app.css
  3. BIN
      html_orig/fontello/fontello.zip
  4. 535
      html_orig/js/app.js
  5. 561
      html_orig/jssrc/term.js
  6. 27
      html_orig/lang/en.php
  7. 120
      html_orig/pages/cfg_term.php
  8. 2
      html_orig/pages/help.php
  9. 28
      html_orig/pages/term.php
  10. 6
      html_orig/sass/_fontello.scss
  11. 24
      html_orig/sass/_term-colors.scss
  12. 1
      html_orig/sass/app.scss
  13. 134
      html_orig/sass/pages/_term.scss
  14. 16
      user/cgi_main.c
  15. 14
      user/cgi_term_cfg.c
  16. 11
      user/persist.c
  17. 2
      user/persist.h
  18. 20
      user/screen.c
  19. 2
      user/screen.h

@ -14,12 +14,13 @@ return [
'%btn4%' => '4',
'%btn5%' => '5',
'%screenData%' => '{
'%screenData%' => '  HELLOx NRE3',//'\u000b\u0001\u001b\u0001\u0001\u0001\u0001\u0001\f\u0005\u0001\u0010\u0003HELLOx\u0002\u000b\u0001\u0001N\u0001RE\u00023\u0001', //,
/*'{
"w": 26, "h": 10,
"x": 0, "y": 0,
"cv": 1,
"screen": "70 t259"
}',
"x": 10, "y": 5,
"cv": 1, "fg": 10, "bg": 2,
"screen": ""
}',//"70 t259"*/
'%opmode%' => '2',
'%sta_enable%' => '0',
@ -65,4 +66,6 @@ return [
'%uart_baud%' => 115200,
'%uart_stopbits%' => 1,
'%uart_parity%' => 2,
'%theme%' => 5,
];

File diff suppressed because one or more lines are too long

Binary file not shown.

@ -1182,306 +1182,337 @@ function tr(key) { return _tr[key] || '?'+key+'?'; }
w.init = wifiInit;
w.startScanning = startScanning;
})(window.WiFi = {});
(function() {
/**
* Terminal module
*/
var Term = (function () {
var W, H;
var cursor = {a: false, x: 0, y: 0, suppress: false, hidden: false};
var screen = [];
var blinkIval;
/*
/!** Clear screen *!/
function cls() {
screen.forEach(function(cell, i) {
cell.t = ' ';
cell.fg = 7;
cell.bg = 0;
blit(cell);
});
}
*/
var Screen = (function () {
var W, H; // dimensions
var inited = false;
var cursor = {
a: false, // active (blink state)
x: 0, // 0-based coordinates
y: 0,
fg: 7, // colors 0-15
bg: 0,
bold: false,
suppress: false, // do not turn on in blink interval (for safe moving)
hidden: false // do not show
};
/** Set text and color at XY */
function cellAt(y, x) {
return screen[y*W+x];
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);
}
}
/** Get cell under cursor */
function cursorCell() {
return cellAt(cursor.y, cursor.x);
}
/** Set text and color at XY */
function _cellAt(y, x) {
return screen[y*W+x];
}
/*
/!** Enable or disable cursor visibility *!/
function cursorEnable(enable) {
cursor.hidden = !enable;
cursor.a &= enable;
blit(cursorCell(), cursor.a);
}
/** Get cell under cursor */
function _curCell() {
return screen[cursor.y*W + cursor.x];
}
/!** Safely move cursor *!/
function cursorSet(y, x) {
// Hide and prevent from showing up during the move
cursor.suppress = true;
blit(cursorCell(), false);
/** Enable or disable cursor visibility */
function _cursorEnable(enable) {
cursor.hidden = !enable;
cursor.a &= enable;
_draw(_curCell());
}
cursor.x = x;
cursor.y = y;
/** Safely move cursor */
function cursorSet(y, x) {
// Hide and prevent from showing up during the move
cursor.suppress = true;
_draw(_curCell(), false);
cursor.x = x;
cursor.y = y;
// Show again
cursor.suppress = false;
_draw(_curCell());
}
// Show again
cursor.suppress = false;
blit(cursorCell(), cursor.a);
}
*/
/** Update cell on display. inv = invert (for cursor) */
function blit(cell, inv) {
var e = cell.e, fg, bg;
// 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;
/** Update cell on display. inv = invert (for cursor) */
function _draw(cell, inv) {
if (typeof inv == 'undefined') {
inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y;
}
/** Show entire screen */
function blitAll() {
screen.forEach(function(cell, i) {
/* Invert if under cursor & cursor active */
var inv = cursor.a && (i == cursor.y*W+cursor.x);
blit(cell, inv);
});
}
var e = cell.e, fg, bg;
// 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' : '');
}
/** Load screen content from a 'binary' sequence */
function load(obj) {
cursor.x = obj.x;
cursor.y = obj.y;
cursor.hidden = !obj.cv;
/** Show entire screen */
function _drawAll() {
for (var i = W*H-1; i>=0; i--) {
_draw(screen[i]);
}
}
// full re-init if size changed
if (obj.w != W || obj.h != H) {
Term.init(obj);
return;
}
function _rebuild(rows, cols) {
W = cols;
H = rows;
// Simple compression - hexFG hexBG 'ASCII' (r/s/t/u NUM{1,2,3,4})?
// comma instead of both colors = same as before
/* Build screen & show */
var e, cell, scr = qs('#screen');
var i = 0, ci = 0, str = obj.screen;
var fg = 7, bg = 0;
while(i < str.length && ci<W*H) {
var cell = screen[ci++];
// Empty the screen node
while (scr.firstChild) scr.removeChild(scr.firstChild);
var j = str[i];
if (j != ',') { // comma = repeat last colors
fg = cell.fg = parseInt(str[i++], 16);
bg = cell.bg = parseInt(str[i++], 16);
} else {
i++;
cell.fg = fg;
cell.bg = bg;
}
screen = [];
var t = cell.t = str[i++];
for(var i = 0; i < W*H; i++) {
e = mk('span');
var repchars = 0;
switch(str[i]) {
case 'r': repchars = 1; break;
case 's': repchars = 2; break;
case 't': repchars = 3; break;
case 'u': repchars = 4; break;
default: repchars = 0;
}
(function() {
var x = i % W;
var y = Math.floor(i / W);
e.addEventListener('click', function () {
Input.onTap(y, x);
});
})();
if (repchars > 0) {
var rep = parseInt(str.substr(i+1,repchars));
i = i + repchars + 1;
for (; rep>0 && ci<W*H; rep--) {
cell = screen[ci++];
cell.fg = fg;
cell.bg = bg;
cell.t = t;
}
}
/* End of line */
if ((i > 0) && (i % W == 0)) {
scr.appendChild(mk('br'));
}
blitAll();
/* The cell */
scr.appendChild(e);
cell = {
t: ' ',
fg: cursor.fg,
bg: cursor.bg,
e: e,
x: i % W,
y: Math.floor(i / W),
};
screen.push(cell);
_draw(cell);
}
}
/** Init the terminal */
function init(obj) {
W = obj.w;
H = obj.h;
/** Init the terminal */
function _init() {
/* Cursor blinking */
clearInterval(blinkIval);
blinkIval = setInterval(function () {
cursor.a = !cursor.a;
if (cursor.hidden) {
cursor.a = false;
}
/* Build screen & show */
var e, cell, scr = qs('#screen');
if (!cursor.suppress) {
_draw(_curCell(), cursor.a);
}
}, 500);
inited = true;
}
// Empty the screen node
while (scr.firstChild) scr.removeChild(scr.firstChild);
/** Decode two-byte number */
function parse2B(s, i) {
return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127;
}
screen = [];
var SEQ_SET_COLOR = 1;
var SEQ_REPEAT = 2;
for(var i = 0; i < W*H; i++) {
e = mk('span');
/** Load screen content from a binary sequence (new) */
function load(str) {
var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, bold, cell;
(function() {
var x = i % W;
var y = Math.floor(i / W);
e.addEventListener('click', function () {
Input.onTap(y, x);
});
})();
if (!inited) _init();
/* End of line */
if ((i > 0) && (i % W == 0)) {
scr.appendChild(mk('br'));
// Set size
num = parse2B(str, i); i += 2;
num2 = parse2B(str, i); i += 2;
if (num != H || num2 != W) {
_rebuild(num, num2);
}
console.log("Size ",num, num2);
// Cursor position
num = parse2B(str, i); i += 2;
num2 = parse2B(str, i); i += 2;
cursorSet(num, num2);
console.log("Cursor at ",num, num2);
// Attributes
num = parse2B(str, i); i += 2;
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);
fg = cursor.fg;
bg = cursor.bg;
bold = cursor.bold;
// Here come the content
while(i < str.length && ci<W*H) {
j = str[i++];
jc = j.charCodeAt(0);
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_REPEAT) {
num = parse2B(str, i); i += 2;
console.log("Repeat x ",num);
for (; num>0 && ci<W*H; num--) {
cell = screen[ci++];
cell.fg = fg;
cell.bg = bg;
cell.t = t;
cell.bold = bold;
}
/* The cell */
scr.appendChild(e);
cell = {t: ' ', fg: 7, bg: 0, e: e};
screen.push(cell);
blit(cell);
}
else {
cell = screen[ci++];
// Unique cell character
t = cell.t = j;
cell.fg = fg;
cell.bg = bg;
cell.bold = bold;
console.log("Symbol ", j);
}
}
/* Cursor blinking */
clearInterval(blinkIval);
blinkIval = setInterval(function() {
cursor.a = !cursor.a;
if (cursor.hidden) {
cursor.a = false;
}
_drawAll();
}
if (!cursor.suppress) {
blit(cursorCell(), cursor.a);
}
}, 500);
return {
load: load, // full load (string)
};
})();
load(obj);
}
/** Handle connections */
var Conn = (function() {
var ws;
// publish
return {
init: init,
load: load
};
})();
function onOpen(evt) {
console.log("CONNECTED");
}
/** Handle connections */
var Conn = (function() {
var ws;
function onClose(evt) {
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
setTimeout(function() {
init();
}, 1000);
}
function onOpen(evt) {
console.log("CONNECTED");
function onMessage(evt) {
try {
console.log("RX: ", evt.data);
// Assume all our messages are screen updates
Screen.load(evt.data);
} catch(e) {
console.error(e);
}
}
function onClose(evt) {
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
setTimeout(function() {
init();
}, 1000);
function doSend(message) {
console.log("TX: ", message);
if (!ws) return; // for dry testing
if (ws.readyState != 1) {
console.error("Socket not ready");
return;
}
function onMessage(evt) {
try {
console.log("RX: ", evt.data);
// Assume all our messages are screen updates
Term.load(JSON.parse(evt.data));
} catch(e) {
console.error(e);
}
if (typeof message != "string") {
message = JSON.stringify(message);
}
ws.send(message);
}
function doSend(message) {
console.log("TX: ", message);
if (ws.readyState != 1) {
console.error("Socket not ready");
return;
}
if (typeof message != "string") {
message = JSON.stringify(message);
}
ws.send(message);
}
function init() {
ws = new WebSocket("ws://"+_root+"/ws/update.cgi");
ws.onopen = onOpen;
ws.onclose = onClose;
ws.onmessage = onMessage;
function init() {
ws = new WebSocket("ws://"+_root+"/ws/update.cgi");
ws.onopen = onOpen;
ws.onclose = onClose;
ws.onmessage = onMessage;
console.log("Opening socket.");
}
console.log("Opening socket.");
}
return {
ws: null,
init: init,
send: doSend
};
})();
return {
ws: null,
init: init,
send: doSend
};
})();
//
// Keyboard (& mouse) input
//
var Input = (function() {
function sendStrMsg(str) {
Conn.send("STR:"+str);
}
/** User input */
var Input = (function() {
function sendStrMsg(str) {
Conn.send("STR:"+str);
}
function sendPosMsg(y, x) {
Conn.send("TAP:"+y+','+x);
}
function sendPosMsg(y, x) {
Conn.send("TAP:"+y+','+x);
}
function sendBtnMsg(n) {
Conn.send("BTN:"+n);
}
function sendBtnMsg(n) {
Conn.send("BTN:"+n);
}
function init() {
window.addEventListener('keypress', function(e) {
var code = +e.which;
if (code >= 32 && code < 127) {
var ch = String.fromCharCode(code);
//console.log("Typed ", ch, "code", code, e);
sendStrMsg(ch);
}
});
function init() {
window.addEventListener('keypress', function(e) {
var code = +e.which;
if (code >= 32 && code < 127) {
var ch = String.fromCharCode(code);
//console.log("Typed ", ch, "code", code, e);
sendStrMsg(ch);
}
});
window.addEventListener('keydown', function(e) {
var code = e.keyCode;
//console.log("Down ", code, e);
switch(code) {
case 8: sendStrMsg('\x08'); break;
case 13: sendStrMsg('\x0d\x0a'); break;
case 27: sendStrMsg('\x1b'); break; // this allows to directly enter control sequences
case 37: sendStrMsg('\x1b[D'); break;
case 38: sendStrMsg('\x1b[A'); break;
case 39: sendStrMsg('\x1b[C'); break;
case 40: sendStrMsg('\x1b[B'); break;
}
});
window.addEventListener('keydown', function(e) {
var code = e.keyCode;
//console.log("Down ", code, e);
switch(code) {
case 8: sendStrMsg('\x08'); break;
case 10:
case 13: sendStrMsg('\x0d\x0a'); break;
case 27: sendStrMsg('\x1b'); break; // this allows to directly enter control sequences
case 37: sendStrMsg('\x1b[D'); break;
case 38: sendStrMsg('\x1b[A'); break;
case 39: sendStrMsg('\x1b[C'); break;
case 40: sendStrMsg('\x1b[B'); break;
}
});
qsa('#buttons button').forEach(function(s) {
s.addEventListener('click', function() {
sendBtnMsg(+this.dataset['n']);
});
qsa('#buttons button').forEach(function(s) {
s.addEventListener('click', function() {
sendBtnMsg(+this.dataset['n']);
});
}
return {
init: init,
onTap: sendPosMsg
};
})();
});
}
window.termInit = function (obj) {
Term.init(obj);
Conn.init();
Input.init();
return {
init: init,
onTap: sendPosMsg
};
})();
window.termInit = function (str) {
Screen.load(str);
Conn.init();
Input.init();
};

@ -1,303 +1,334 @@
(function() {
/**
* Terminal module
*/
var Term = (function () {
var W, H;
var cursor = {a: false, x: 0, y: 0, suppress: false, hidden: false};
var screen = [];
var blinkIval;
/*
/!** Clear screen *!/
function cls() {
screen.forEach(function(cell, i) {
cell.t = ' ';
cell.fg = 7;
cell.bg = 0;
blit(cell);
});
}
*/
/** Set text and color at XY */
function cellAt(y, x) {
return screen[y*W+x];
}
/** Get cell under cursor */
function cursorCell() {
return cellAt(cursor.y, cursor.x);
}
/*
/!** Enable or disable cursor visibility *!/
function cursorEnable(enable) {
cursor.hidden = !enable;
cursor.a &= enable;
blit(cursorCell(), cursor.a);
}
/!** Safely move cursor *!/
function cursorSet(y, x) {
// Hide and prevent from showing up during the move
cursor.suppress = true;
blit(cursorCell(), false);
cursor.x = x;
cursor.y = y;
var Screen = (function () {
var W, H; // dimensions
var inited = false;
var cursor = {
a: false, // active (blink state)
x: 0, // 0-based coordinates
y: 0,
fg: 7, // colors 0-15
bg: 0,
bold: false,
suppress: false, // do not turn on in blink interval (for safe moving)
hidden: false // do not show
};
// Show again
cursor.suppress = false;
blit(cursorCell(), cursor.a);
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);
}
*/
/** Update cell on display. inv = invert (for cursor) */
function blit(cell, inv) {
var e = cell.e, fg, bg;
// 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;
}
/** Set text and color at XY */
function _cellAt(y, x) {
return screen[y*W+x];
}
/** 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
cursor.suppress = true;
_draw(_curCell(), false);
cursor.x = x;
cursor.y = y;
// Show again
cursor.suppress = false;
_draw(_curCell());
}
/** Update cell on display. inv = invert (for cursor) */
function _draw(cell, inv) {
if (typeof inv == 'undefined') {
inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y;
}
/** Show entire screen */
function blitAll() {
screen.forEach(function(cell, i) {
/* Invert if under cursor & cursor active */
var inv = cursor.a && (i == cursor.y*W+cursor.x);
blit(cell, inv);
});
var e = cell.e, fg, bg;
// 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' : '');
}
/** Show entire screen */
function _drawAll() {
for (var i = W*H-1; i>=0; i--) {
_draw(screen[i]);
}
}
/** Load screen content from a 'binary' sequence */
function load(obj) {
cursor.x = obj.x;
cursor.y = obj.y;
cursor.hidden = !obj.cv;
function _rebuild(rows, cols) {
W = cols;
H = rows;
// full re-init if size changed
if (obj.w != W || obj.h != H) {
Term.init(obj);
return;
}
/* Build screen & show */
var e, cell, scr = qs('#screen');
// Simple compression - hexFG hexBG 'ASCII' (r/s/t/u NUM{1,2,3,4})?
// comma instead of both colors = same as before
// Empty the screen node
while (scr.firstChild) scr.removeChild(scr.firstChild);
var i = 0, ci = 0, str = obj.screen;
var fg = 7, bg = 0;
while(i < str.length && ci<W*H) {
var cell = screen[ci++];
screen = [];
var j = str[i];
if (j != ',') { // comma = repeat last colors
fg = cell.fg = parseInt(str[i++], 16);
bg = cell.bg = parseInt(str[i++], 16);
} else {
i++;
cell.fg = fg;
cell.bg = bg;
}
for(var i = 0; i < W*H; i++) {
e = mk('span');
var t = cell.t = str[i++];
var repchars = 0;
switch(str[i]) {
case 'r': repchars = 1; break;
case 's': repchars = 2; break;
case 't': repchars = 3; break;
case 'u': repchars = 4; break;
default: repchars = 0;
}
(function() {
var x = i % W;
var y = Math.floor(i / W);
e.addEventListener('click', function () {
Input.onTap(y, x);
});
})();
if (repchars > 0) {
var rep = parseInt(str.substr(i+1,repchars));
i = i + repchars + 1;
for (; rep>0 && ci<W*H; rep--) {
cell = screen[ci++];
cell.fg = fg;
cell.bg = bg;
cell.t = t;
}
}
/* End of line */
if ((i > 0) && (i % W == 0)) {
scr.appendChild(mk('br'));
}
blitAll();
/* The cell */
scr.appendChild(e);
cell = {
t: ' ',
fg: cursor.fg,
bg: cursor.bg,
e: e,
x: i % W,
y: Math.floor(i / W),
};
screen.push(cell);
_draw(cell);
}
/** Init the terminal */
function init(obj) {
W = obj.w;
H = obj.h;
/* Build screen & show */
var e, cell, scr = qs('#screen');
// Empty the screen node
while (scr.firstChild) scr.removeChild(scr.firstChild);
screen = [];
for(var i = 0; i < W*H; i++) {
e = mk('span');
(function() {
var x = i % W;
var y = Math.floor(i / W);
e.addEventListener('click', function () {
Input.onTap(y, x);
});
})();
/* End of line */
if ((i > 0) && (i % W == 0)) {
scr.appendChild(mk('br'));
}
/* The cell */
scr.appendChild(e);
cell = {t: ' ', fg: 7, bg: 0, e: e};
screen.push(cell);
blit(cell);
}
/** Init the terminal */
function _init() {
/* Cursor blinking */
clearInterval(blinkIval);
blinkIval = setInterval(function () {
cursor.a = !cursor.a;
if (cursor.hidden) {
cursor.a = false;
}
/* Cursor blinking */
clearInterval(blinkIval);
blinkIval = setInterval(function() {
cursor.a = !cursor.a;
if (cursor.hidden) {
cursor.a = false;
}
if (!cursor.suppress) {
blit(cursorCell(), cursor.a);
}
}, 500);
if (!cursor.suppress) {
_draw(_curCell(), cursor.a);
}
}, 500);
inited = true;
}
load(obj);
}
/** Decode two-byte number */
function parse2B(s, i) {
return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127;
}
// publish
return {
init: init,
load: load
};
})();
var SEQ_SET_COLOR = 1;
var SEQ_REPEAT = 2;
/** Handle connections */
var Conn = (function() {
var ws;
/** Load screen content from a binary sequence (new) */
function load(str) {
var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, bold, cell;
function onOpen(evt) {
console.log("CONNECTED");
}
if (!inited) _init();
function onClose(evt) {
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
setTimeout(function() {
init();
}, 1000);
// Set size
num = parse2B(str, i); i += 2;
num2 = parse2B(str, i); i += 2;
if (num != H || num2 != W) {
_rebuild(num, num2);
}
function onMessage(evt) {
try {
console.log("RX: ", evt.data);
// Assume all our messages are screen updates
Term.load(JSON.parse(evt.data));
} catch(e) {
console.error(e);
console.log("Size ",num, num2);
// Cursor position
num = parse2B(str, i); i += 2;
num2 = parse2B(str, i); i += 2;
cursorSet(num, num2);
console.log("Cursor at ",num, num2);
// Attributes
num = parse2B(str, i); i += 2;
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);
fg = cursor.fg;
bg = cursor.bg;
bold = cursor.bold;
// Here come the content
while(i < str.length && ci<W*H) {
j = str[i++];
jc = j.charCodeAt(0);
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);
}
}
function doSend(message) {
console.log("TX: ", message);
if (ws.readyState != 1) {
console.error("Socket not ready");
return;
else if (jc == SEQ_REPEAT) {
num = parse2B(str, i); i += 2;
console.log("Repeat x ",num);
for (; num>0 && ci<W*H; num--) {
cell = screen[ci++];
cell.fg = fg;
cell.bg = bg;
cell.t = t;
cell.bold = bold;
}
}
if (typeof message != "string") {
message = JSON.stringify(message);
else {
cell = screen[ci++];
// Unique cell character
t = cell.t = j;
cell.fg = fg;
cell.bg = bg;
cell.bold = bold;
console.log("Symbol ", j);
}
ws.send(message);
}
function init() {
ws = new WebSocket("ws://"+_root+"/ws/update.cgi");
ws.onopen = onOpen;
ws.onclose = onClose;
ws.onmessage = onMessage;
_drawAll();
}
console.log("Opening socket.");
}
return {
load: load, // full load (string)
};
})();
return {
ws: null,
init: init,
send: doSend
};
})();
//
// Keyboard (& mouse) input
//
var Input = (function() {
function sendStrMsg(str) {
Conn.send("STR:"+str);
/** Handle connections */
var Conn = (function() {
var ws;
function onOpen(evt) {
console.log("CONNECTED");
}
function onClose(evt) {
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
setTimeout(function() {
init();
}, 1000);
}
function onMessage(evt) {
try {
console.log("RX: ", evt.data);
// Assume all our messages are screen updates
Screen.load(evt.data);
} catch(e) {
console.error(e);
}
function sendPosMsg(y, x) {
Conn.send("TAP:"+y+','+x);
}
function doSend(message) {
console.log("TX: ", message);
if (!ws) return; // for dry testing
if (ws.readyState != 1) {
console.error("Socket not ready");
return;
}
function sendBtnMsg(n) {
Conn.send("BTN:"+n);
if (typeof message != "string") {
message = JSON.stringify(message);
}
ws.send(message);
}
function init() {
ws = new WebSocket("ws://"+_root+"/ws/update.cgi");
ws.onopen = onOpen;
ws.onclose = onClose;
ws.onmessage = onMessage;
console.log("Opening socket.");
}
return {
ws: null,
init: init,
send: doSend
};
})();
function init() {
window.addEventListener('keypress', function(e) {
var code = +e.which;
if (code >= 32 && code < 127) {
var ch = String.fromCharCode(code);
//console.log("Typed ", ch, "code", code, e);
sendStrMsg(ch);
}
});
window.addEventListener('keydown', function(e) {
var code = e.keyCode;
//console.log("Down ", code, e);
switch(code) {
case 8: sendStrMsg('\x08'); break;
case 13: sendStrMsg('\x0d\x0a'); break;
case 27: sendStrMsg('\x1b'); break; // this allows to directly enter control sequences
case 37: sendStrMsg('\x1b[D'); break;
case 38: sendStrMsg('\x1b[A'); break;
case 39: sendStrMsg('\x1b[C'); break;
case 40: sendStrMsg('\x1b[B'); break;
}
});
/** User input */
var Input = (function() {
function sendStrMsg(str) {
Conn.send("STR:"+str);
}
function sendPosMsg(y, x) {
Conn.send("TAP:"+y+','+x);
}
function sendBtnMsg(n) {
Conn.send("BTN:"+n);
}
function init() {
window.addEventListener('keypress', function(e) {
var code = +e.which;
if (code >= 32 && code < 127) {
var ch = String.fromCharCode(code);
//console.log("Typed ", ch, "code", code, e);
sendStrMsg(ch);
}
});
window.addEventListener('keydown', function(e) {
var code = e.keyCode;
//console.log("Down ", code, e);
switch(code) {
case 8: sendStrMsg('\x08'); break;
case 10:
case 13: sendStrMsg('\x0d\x0a'); break;
case 27: sendStrMsg('\x1b'); break; // this allows to directly enter control sequences
case 37: sendStrMsg('\x1b[D'); break;
case 38: sendStrMsg('\x1b[A'); break;
case 39: sendStrMsg('\x1b[C'); break;
case 40: sendStrMsg('\x1b[B'); break;
}
});
qsa('#buttons button').forEach(function(s) {
s.addEventListener('click', function() {
sendBtnMsg(+this.dataset['n']);
});
qsa('#buttons button').forEach(function(s) {
s.addEventListener('click', function() {
sendBtnMsg(+this.dataset['n']);
});
}
});
}
return {
init: init,
onTap: sendPosMsg
};
})();
window.termInit = function (obj) {
Term.init(obj);
Conn.init();
Input.init();
return {
init: init,
onTap: sendPosMsg
};
})();
window.termInit = function (str) {
Screen.load(str);
Conn.init();
Input.init();
};

@ -27,27 +27,22 @@ return [
Those are the initial settings used after ESPTerm powers on. They
will also be applied immediately after you submit this form.',
'term.example' => 'Color settings example',
'term.example' => 'Default colors preview',
'term.term_title' => 'Header text',
'term.term_width' => 'Screen width',
'term.term_height' => 'Screen height',
'term.default_fg' => 'Text color',
'term.default_bg' => 'Background',
'term.btn1' => 'Button 1 text',
'term.btn2' => 'Button 2 text',
'term.btn3' => 'Button 3 text',
'term.btn4' => 'Button 4 text',
'term.btn5' => 'Button 5 text',
'term.term_width' => 'Screen width / height',
'term.default_fg_bg' => 'Text / background',
'term.buttons' => 'Button labels',
'term.theme' => 'Color scheme',
// terminal color labels
'color.0' => 'Black',
'color.1' => 'Dark Red',
'color.2' => 'Dark Green',
'color.3' => 'Dim Yellow',
'color.4' => 'Deep Blue',
'color.5' => 'Deep Purple',
'color.6' => 'Dark Cyan',
'color.1' => 'Red',
'color.2' => 'Green',
'color.3' => 'Yellow',
'color.4' => 'Blue',
'color.5' => 'Purple',
'color.6' => 'Cyan',
'color.7' => 'Silver',
'color.8' => 'Gray',
'color.9' => 'Light Red',

@ -6,21 +6,78 @@
</div>
<div class="Row">
<div style="font-family:monospace; font-size: 16pt; padding: 5px;" id="color-example"><?= tr("term.example") ?></div>
<label for="theme"><?= tr("term.theme") ?></label>
<select name="theme" id="theme" class="short" onchange="showColor()">
<option value="0">Tango</option>
<option value="1">Linux</option>
<option value="2">XTerm</option>
<option value="3">Rxvt</option>
<option value="4">Ambience</option>
<option value="5">Solarized</option>
</select>
</div>
<div class="Row color-preview">
<div class="colorprev">
<span data-fg=0 class="bg0 fg0">30</span><!--
--><span data-fg=1 class="bg0 fg1">31</span><!--
--><span data-fg=2 class="bg0 fg2">32</span><!--
--><span data-fg=3 class="bg0 fg3">33</span><!--
--><span data-fg=4 class="bg0 fg4">34</span><!--
--><span data-fg=5 class="bg0 fg5">35</span><!--
--><span data-fg=6 class="bg0 fg6">36</span><!--
--><span data-fg=7 class="bg0 fg7">37</span>
</div>
<div class="colorprev">
<span data-fg=8 class="bg0 fg8">90</span><!--
--><span data-fg=9 class="bg0 fg9">91</span><!--
--><span data-fg=10 class="bg0 fg10">92</span><!--
--><span data-fg=11 class="bg0 fg11">93</span><!--
--><span data-fg=12 class="bg0 fg12">94</span><!--
--><span data-fg=13 class="bg0 fg13">95</span><!--
--><span data-fg=14 class="bg0 fg14">96</span><!--
--><span data-fg=15 class="bg0 fg15">97</span>
</div>
<div class="colorprev">
<span data-bg=0 class="bg0 fg15">40</span><!--
--><span data-bg=1 class="bg1 fg15">41</span><!--
--><span data-bg=2 class="bg2 fg15">42</span><!--
--><span data-bg=3 class="bg3 fg0">43</span><!--
--><span data-bg=4 class="bg4 fg15">44</span><!--
--><span data-bg=5 class="bg5 fg15">45</span><!--
--><span data-bg=6 class="bg6 fg15">46</span><!--
--><span data-bg=7 class="bg7 fg0">47</span>
</div>
<div class="colorprev">
<span data-bg=8 class="bg8 fg15">100</span><!--
--><span data-bg=9 class="bg9 fg0">101</span><!--
--><span data-bg=10 class="bg10 fg0">102</span><!--
--><span data-bg=11 class="bg11 fg0">103</span><!--
--><span data-bg=12 class="bg12 fg0">104</span><!--
--><span data-bg=13 class="bg13 fg0">105</span><!--
--><span data-bg=14 class="bg14 fg0">106</span><!--
--><span data-bg=15 class="bg15 fg0">107</span>
</div>
</div>
<div class="Row color-preview">
<div style="
" id="color-example">
<?= tr("term.example") ?>
</div>
</div>
<div class="Row">
<label for="default_fg"><?= tr("term.default_fg") ?></label>
<label><?= tr("term.default_fg_bg") ?></label>
<select name="default_fg" id="default_fg" class="short" onchange="showColor()">
<?php for($i=0; $i<16; $i++): ?>
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
<?php endfor; ?>
</select>
</div>
<div class="Row">
<label for="default_bg"><?= tr("term.default_bg") ?></label>
<select name="default_bg" id="default_bg" class="short" onchange="showColor()">
</select>&nbsp;<!--
--><select name="default_bg" id="default_bg" class="short" onchange="showColor()">
<?php for($i=0; $i<16; $i++): ?>
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
<?php endfor; ?>
@ -29,12 +86,8 @@
<div class="Row">
<label for="term_width"><?= tr('term.term_width') ?></label>
<input type="number" step=1 min=1 max=255 name="term_width" id="term_width" value="%term_width%" required>
</div>
<div class="Row">
<label for="term_height"><?= tr('term.term_height') ?></label>
<input type="number" step=1 min=1 max=255 name="term_height" id="term_height" value="%term_height%" required>
<input type="number" step=1 min=1 max=255 name="term_width" id="term_width" value="%term_width%" required>&nbsp;<!--
--><input type="number" step=1 min=1 max=255 name="term_height" id="term_height" value="%term_height%" required>
</div>
<div class="Row">
@ -43,27 +96,11 @@
</div>
<div class="Row">
<label for="btn1"><?= tr("term.btn1") ?></label>
<input class="short" type="text" name="btn1" id="btn1" value="%btn1%">
</div>
<div class="Row">
<label for="btn2"><?= tr("term.btn2") ?></label>
<input class="short" type="text" name="btn2" id="btn2" value="%btn2%">
</div>
<div class="Row">
<label for="btn3"><?= tr("term.btn3") ?></label>
<input class="short" type="text" name="btn3" id="btn3" value="%btn3%">
</div>
<div class="Row">
<label for="btn4"><?= tr("term.btn4") ?></label>
<input class="short" type="text" name="btn4" id="btn4" value="%btn4%">
</div>
<div class="Row">
<label for="btn5"><?= tr("term.btn5") ?></label>
<label><?= tr("term.buttons") ?></label>
<input class="short" type="text" name="btn1" id="btn1" value="%btn1%">&nbsp;
<input class="short" type="text" name="btn2" id="btn2" value="%btn2%">&nbsp;
<input class="short" type="text" name="btn3" id="btn3" value="%btn3%">&nbsp;
<input class="short" type="text" name="btn4" id="btn4" value="%btn4%">&nbsp;
<input class="short" type="text" name="btn5" id="btn5" value="%btn5%">
</div>
@ -75,12 +112,25 @@
<script>
$('#default_fg').val(%default_fg%);
$('#default_bg').val(%default_bg%);
$('#theme').val(%theme%);
function showColor() {
var ex = qs('#color-example');
ex.className = '';
ex.classList.add('fg'+$('#default_fg').val());
ex.classList.add('bg'+$('#default_bg').val());
var th = $('#theme').val();
$('.color-preview').forEach(function(e) {
e.className = 'Row color-preview theme-'+th;
});
}
showColor();
$('.colorprev span').on('click', function() {
var fg = this.dataset.fg;
var bg = this.dataset.bg;
if (typeof fg != 'undefined') $('#default_fg').val(fg);
if (typeof bg != 'undefined') $('#default_bg').val(bg);
showColor()
});
</script>

@ -178,7 +178,7 @@
<tr>
<td>\e[&lt;n&gt;E</td>
<td>[count]</td>
<td>Go N line down, start of line</td>
<td>Go N lines down, start of line</td>
</tr>
<tr>
<td>\e[&lt;n&gt;F</td>

@ -2,6 +2,7 @@
// Workaround for badly loaded page
setTimeout(function() {
if (typeof termInit == 'undefined' || typeof $ == 'undefined') {
console.error("Page load failed, refreshing…");
location.reload(true);
}
}, 2000);
@ -10,7 +11,7 @@
<h1>%term_title%</h1>
<div id="termwrap">
<div id="screen"></div>
<div id="screen" class="theme-%theme%"></div>
<div id="buttons">
<button data-n="1" class="btn-blue">%btn1%</button><!--
@ -21,17 +22,32 @@
</div>
</div>
<input id="softkb-input" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
<nav id="botnav">
<a href="<?= url('cfg_wifi') ?>"><?= tr('menu.settings') ?></a><!--
--><a href="<?= url('help') ?>">Help</a><!--
--><a href="<?= url('about') ?>">About</a>
<a href="#" onclick="toggleSoftKb(true); return false" class="icn-keyboard mq-tablet-max"></a><!--
--><a href="<?= url('cfg_wifi') ?>"><?= tr('menu.settings') ?></a><!--
--><a href="<?= url('help') ?>">Help</a><!--
--><a href="<?= url('about') ?>">About</a>
</nav>
<script>
// TODO cleanup
try {
termInit(%screenData%);
termInit("%screenData%");
// auto-clear the input box
$('#softkb-input').on('input', function(e) {
setTimeout(function(){$('#softkb-input').val('');}, 1);
});
} catch(e) {
console.error("Fail, reloading...");
console.error(e);
console.error("Fail, reloading…");
location.reload(true);
}
function toggleSoftKb(yes) {
qs('#softkb-input')[yes ? 'focus' : 'blur']();
qs('.icn-keyboard').blur();
}
</script>

File diff suppressed because one or more lines are too long

@ -1,24 +0,0 @@
$term-colors:
// 0 black, 1 red, 2 green, 3 yellow
// 4 blue, 5 mag, 6 cyan, 7 white
#111213, #CC0000, #4E9A06, #C4A000,
#3465A4, #75507B, #06989A, #D3D7CF,
// BRIGHT
// 8 black, 9 red, 10 green, 11 yellow
// 12 blue, 13 mag, 14 cyan, 15 white
#555753, #EF2929, #8AE234, #FCE94F,
#729FCF, #AD7FA8, #34E2E2, #EEEEEC;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
.fg8, .fg9, .fg10, .fg11, .fg12, .fg13, .fg14, .fg15 {
font-weight: bold;
}
.nb {
font-weight: normal !important;
}

@ -33,7 +33,6 @@ $c-form-highlight-a: #2ea1f9;
@import "pages/wifi";
@import "pages/term";
@import "pages/about";
@import "term-colors";
// media queries

@ -1,8 +1,13 @@
body.term {
h1 {
font-size: fsize(5);
@include media($phone) {
font-size: fsize(3);
#content {
padding-left: 0;
padding-right: 0;
h1 {
font-size: fsize(5);
@include media($phone) {
font-size: fsize(3);
}
}
}
@ -36,7 +41,7 @@ body.term {
margin: 0 3px;
padding: 8px 5px;
//width: 18%;
min-width: 65px;
min-width: 62px;
//max-width: 65px;
//min-width: initial;
cursor: pointer;
@ -53,16 +58,133 @@ body.term {
text-decoration: underline;
&, &:visited, &:link {
color: #2e4d6e;
color: #336085;
}
&:hover {
color: #5abfff;
}
}
.icn-keyboard {
text-decoration: none;
font-size: 150%;
vertical-align: middle;
}
}
}
#termwrap {
text-align: center;
}
#softkb-input {
position: absolute;
top: -9999px;
}
// "non-bold"
.nb {
font-weight: normal !important;
}
// Tango
.theme-0 {
$term-colors:
#111213, #CC0000, #4E9A06, #C4A000, #3465A4, #75507B, #06989A, #D3D7CF,
#555753, #EF2929, #8AE234, #FCE94F, #729FCF, #AD7FA8, #34E2E2, #EEEEEC;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// Linux
.theme-1 {
$term-colors:
#000000, #aa0000, #00aa00, #aa5500, #0000aa, #aa00aa, #00aaaa, #aaaaaa,
#555555, #ff5555, #55ff55, #ffff55, #5555ff, #ff55ff, #55ffff, #ffffff;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// xterm
.theme-2 {
$term-colors:
#000000, #cd0000, #00cd00, #cdcd00, #0000ee, #cd00cd, #00cdcd, #e5e5e5,
#7f7f7f, #ff0000, #00ff00, #ffff00, #5c5cff, #ff00ff, #00ffff, #ffffff;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// rxvt
.theme-3 {
$term-colors:
#000000, #cd0000, #00cd00, #cdcd00, #0000cd, #cd00cd, #00cdcd, #faebd7,
#404040, #ff0000, #00ff00, #ffff00, #0000ff, #ff00ff, #00ffff, #ffffff;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// Ambience
.theme-4 {
$term-colors:
#2e3436, #cc0000, #4e9a06, #c4a000, #3465a4, #75507b, #06989a, #d3d7cf,
#555753, #ef2929, #8ae234, #fce94f, #729fcf, #ad7fa8, #34e2e2, #eeeeec;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// Solarized
.theme-5 {
$term-colors:
#073642, #dc322f, #859900, #b58900, #268bd2, #d33682, #2aa198, #eee8d5,
#002b36, #cb4b16, #586e75, #657b83, #839496, #6c71c4, #93a1a1, #fdf6e3;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
.bold {
font-weight: bold !important;
}
.Row.color-preview {
font-family: monospace;
font-size: 16pt;
display: block;
margin-bottom: 0;
padding-left: $form-label-w;
@include media($phone) {
padding-left: 0;
font-size: 14pt;
}
.colorprev {
display:block;
margin: 0;
cursor: pointer;
}
}
#color-example {
display: inline-block;
padding: 5px;
}

@ -44,12 +44,16 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplScreen(HttpdConnData *connData, char *token
else if (streq(token, "btn5")) {
httpdSend(connData, termconf->btn5, -1);
}
else if (streq(token, "default_bg")) {
sprintf(buff, "%d", termconf->default_bg);
httpdSend(connData, buff, -1);
}
else if (streq(token, "default_fg")) {
sprintf(buff, "%d", termconf->default_fg);
// else if (streq(token, "default_bg")) {
// sprintf(buff, "%d", termconf->default_bg);
// httpdSend(connData, buff, -1);
// }
// else if (streq(token, "default_fg")) {
// sprintf(buff, "%d", termconf->default_fg);
// httpdSend(connData, buff, -1);
// }
else if (streq(token, "theme")) {
sprintf(buff, "%d", termconf->theme);
httpdSend(connData, buff, -1);
}
else if (streq(token, "screenData")) {

@ -82,6 +82,17 @@ cgiTermCfgSetParams(HttpdConnData *connData)
}
}
if (GET_ARG("theme")) {
dbg("Screen color theme: %s", buff);
int theme = atoi(buff);
if (theme >= 0 && theme <= 5) { // ALWAYS ADJUST WHEN ADDING NEW THEME!
termconf->theme = (u8) theme;
} else {
warn("Bad theme num: %s", buff);
redir_url += sprintf(redir_url, "theme,");
}
}
if (GET_ARG("term_title")) {
dbg("Terminal title default text: \"%s\"", buff);
strncpy_safe(termconf->title, buff, 64); // ATTN those must match the values in
@ -148,6 +159,9 @@ tplTermCfg(HttpdConnData *connData, char *token, void **arg)
else if (streq(token, "term_height")) {
sprintf(buff, "%d", termconf->height);
}
else if (streq(token, "theme")) {
sprintf(buff, "%d", termconf->theme);
}
else if (streq(token, "default_bg")) {
sprintf(buff, "%d", termconf->default_bg);
}

@ -105,7 +105,15 @@ persist_load(void)
}
if (hard_reset) {
// Zero all out
memset(&persist, 0, sizeof(PersistBlock));
persist_load_hard_default();
// write them also as defaults
memcpy(&persist.defaults, &persist.current, sizeof(AppConfigBundle));
persist_store();
// this also stores them to flash and applies to modules
} else {
apply_live_settings();
@ -141,9 +149,6 @@ persist_load_hard_default(void)
restore_live_settings_to_hard_defaults();
persist_store();
// // Store current -> default
// memcpy(&persist.defaults, &persist.current, sizeof(AppConfigBundle));
info("[Persist] Settings restored to hard defaults.");
apply_live_settings(); // apply

@ -16,7 +16,7 @@
// Changing this could be used to force-erase the config area
// after a firmware upgrade
#define CHECKSUM_SALT 1
#define CHECKSUM_SALT 2
#define APPCONF_SIZE 2048

@ -671,7 +671,25 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data)
ss->lastFg = 0;
ss->lastChar = '\0';
bufprint("{\n \"w\": %d, \"h\": %d,\n \"x\": %d, \"y\": %d,\n \"cv\": %d,\n \"screen\": \"", W, H, cursor.x, cursor.y, cursor.visible);
// TODO implement the new more efficient encoder!
bufprint(
"{"
"\"w\":%d,"
"\"h\":%d,"
"\"x\":%d,"
"\"y\":%d,"
"\"fg\":%d,"
"\"bg\":%d,"
"\"cv\":%d,"
"\"screen\":\"",
W,
H,
cursor.x,
cursor.y,
cursor.fg,
cursor.bg,
cursor.visible);
}
int i = ss->index;

@ -52,6 +52,7 @@ typedef struct {
char btn3[TERM_BTN_LEN];
char btn4[TERM_BTN_LEN];
char btn5[TERM_BTN_LEN];
u8 theme;
u8 _filler[
TERMCONF_SIZE
@ -59,6 +60,7 @@ typedef struct {
- 4
- 1
- 1
- 1
- TERM_TITLE_LEN
- TERM_BTN_LEN * 5
];

Loading…
Cancel
Save