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