From 48cd9c1a2a7da9d46bd9afa024a6cc4e9a011a60 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?=
Date: Thu, 7 Sep 2017 02:18:28 +0200
Subject: [PATCH] fine-tuned upload system
---
html_orig/js/app.js | 1367 +++++++++++++++++---------------
html_orig/jssrc/term.js | 834 -------------------
html_orig/jssrc/term_conn.js | 122 +++
html_orig/jssrc/term_input.js | 262 ++++++
html_orig/jssrc/term_screen.js | 377 +++++++++
html_orig/jssrc/term_upload.js | 146 ++++
html_orig/packjs.sh | 1 +
html_orig/pages/term.php | 2 +-
user/cgi_sockets.c | 31 +-
user/cgi_sockets.h | 2 +
user/uart_buffer.c | 39 +-
user/uart_buffer.h | 4 +
12 files changed, 1693 insertions(+), 1494 deletions(-)
create mode 100644 html_orig/jssrc/term_conn.js
create mode 100644 html_orig/jssrc/term_input.js
create mode 100644 html_orig/jssrc/term_screen.js
create mode 100644 html_orig/jssrc/term_upload.js
diff --git a/html_orig/js/app.js b/html_orig/js/app.js
index 07955d5..e664544 100644
--- a/html_orig/js/app.js
+++ b/html_orig/js/app.js
@@ -1604,747 +1604,780 @@ function tr(key) { return _tr[key] || '?'+key+'?'; }
w.init = wifiInit;
w.startScanning = startScanning;
})(window.WiFi = {});
-var Screen = (function () {
- var W = 0, H = 0; // 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,
- attrs: 0,
- suppress: false, // do not turn on in blink interval (for safe moving)
- forceOn: false, // force on unless hanging: used to keep cursor visible during move
- hidden: false, // do not show (DEC opt)
- hanging: false, // cursor at column "W+1" - not visible
- };
-
- var screen = [];
- var blinkIval;
- var cursorFlashStartIval;
+/** Handle connections */
+var Conn = (function() {
+ var ws;
+ var heartbeatTout;
+ var pingIv;
+ var xoff = false;
+ var autoXoffTout;
- // Some non-bold Fraktur symbols are outside the contiguous block
- var frakturExceptions = {
- 'C': '\u212d',
- 'H': '\u210c',
- 'I': '\u2111',
- 'R': '\u211c',
- 'Z': '\u2128',
- };
+ function onOpen(evt) {
+ console.log("CONNECTED");
+ }
- // for BEL
- var audioCtx = null;
- try {
- audioCtx = new (window.AudioContext || window.audioContext || window.webkitAudioContext)();
- } catch (er) {
- console.error("No AudioContext!", er);
+ function onClose(evt) {
+ console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
+ setTimeout(function() {
+ init();
+ }, 200);
+ // this happens when the buffer gets fucked up via invalid unicode.
+ // we basically use polling instead of socket then
}
- /** Get cell under cursor */
- function _curCell() {
- return screen[cursor.y*W + cursor.x];
+ function onMessage(evt) {
+ try {
+ // . = heartbeat
+ switch (evt.data.charAt(0)) {
+ case 'B':
+ case 'T':
+ case 'S':
+ Screen.load(evt.data);
+ break;
+
+ case '-':
+ //console.log('xoff');
+ xoff = true;
+ autoXoffTout = setTimeout(function(){xoff=false;}, 250);
+ break;
+
+ case '+':
+ //console.log('xon');
+ xoff = false;
+ clearTimeout(autoXoffTout);
+ break;
+ }
+ heartbeat();
+ } catch(e) {
+ console.error(e);
+ }
}
- /** 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());
+ function canSend() {
+ return !xoff;
}
- function alpha2fraktur(t) {
- // 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));
- }
+ function doSend(message) {
+ //console.log("TX: ", message);
+ if (xoff) {
+ // TODO queue
+ console.log("Can't send, flood control.");
+ return false;
}
- return t;
- }
- /** Update cell on display. inv = invert (for cursor) */
- function _draw(cell, inv) {
- if (!cell) return;
- if (typeof inv == 'undefined') {
- inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y;
+ if (!ws) return false; // for dry testing
+ if (ws.readyState != 1) {
+ console.error("Socket not ready");
+ return false;
}
+ if (typeof message != "string") {
+ message = JSON.stringify(message);
+ }
+ ws.send(message);
+ return true;
+ }
- var fg, bg, cn, t;
+ function init() {
+ heartbeat();
- fg = inv ? cell.bg : cell.fg;
- bg = inv ? cell.fg : cell.bg;
+ ws = new WebSocket("ws://"+_root+"/term/update.ws");
+ ws.onopen = onOpen;
+ ws.onclose = onClose;
+ ws.onmessage = onMessage;
- t = cell.t;
- if (!t.length) t = ' ';
+ console.log("Opening socket.");
- cn = 'fg' + fg + ' bg' + bg;
- if (cell.attrs & (1<<0)) cn += ' bold';
- if (cell.attrs & (1<<1)) cn += ' faint';
- 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';
- t = alpha2fraktur(t);
- }
- if (cell.attrs & (1<<6)) cn += ' strike';
+ // Ask for initial data
+ $.get('http://'+_root+'/term/init', function(resp, status) {
+ if (status !== 200) location.reload(true);
+ console.log("Data received!");
+ Screen.load(resp);
+ heartbeat();
- cell.slot.textContent = t;
- cell.elem.className = cn;
+ showPage();
+ });
}
- /** Show entire screen */
- function _drawAll() {
- for (var i = W*H-1; i>=0; i--) {
- _draw(screen[i]);
- }
+ function heartbeat() {
+ clearTimeout(heartbeatTout);
+ heartbeatTout = setTimeout(heartbeatFail, 2000);
}
- function _rebuild(rows, cols) {
- W = cols;
- H = rows;
+ function heartbeatFail() {
+ console.error("Heartbeat lost, probing server...");
+ pingIv = setInterval(function() {
+ console.log("> ping");
+ $.get('http://'+_root+'/system/ping', function(resp, status) {
+ if (status == 200) {
+ clearInterval(pingIv);
+ console.info("Server ready, reloading page...");
+ location.reload();
+ }
+ }, {
+ timeout: 100,
+ });
+ }, 500);
+ }
- /* Build screen & show */
- var cOuter, cInner, cell, screenDiv = qs('#screen');
+ return {
+ ws: null,
+ init: init,
+ send: doSend,
+ canSend: canSend, // check flood control
+ };
+})();
+/**
+ * User input
+ *
+ * --- Rx messages: ---
+ * S - screen content (binary encoding of the entire screen with simple compression)
+ * T - text labels - Title and buttons, \0x01-separated
+ * B - beep
+ * . - heartbeat
+ *
+ * --- Tx messages ---
+ * s - string
+ * b - action button
+ * p - mb press
+ * r - mb release
+ * m - mouse move
+ */
+var Input = (function() {
+ var opts = {
+ np_alt: false,
+ cu_alt: false,
+ fn_alt: false,
+ mt_click: false,
+ mt_move: false,
+ no_keys: false,
+ };
- // Empty the screen node
- while (screenDiv.firstChild) screenDiv.removeChild(screenDiv.firstChild);
+ /** Send a literal message */
+ function sendStrMsg(str) {
+ return Conn.send("s"+str);
+ }
- screen = [];
+ /** Send a button event */
+ function sendBtnMsg(n) {
+ Conn.send("b"+Chr(n));
+ }
- for(var i = 0; i < W*H; i++) {
- cOuter = mk('span');
- cInner = mk('span');
+ /** Fn alt choice for key message */
+ function fa(alt, normal) {
+ return opts.fn_alt ? alt : normal;
+ }
- /* Mouse tracking */
- (function() {
- var x = i % W;
- var y = Math.floor(i / W);
- cOuter.addEventListener('mouseenter', function (evt) {
- Input.onMouseMove(x, y);
- });
- cOuter.addEventListener('mousedown', function (evt) {
- Input.onMouseDown(x, y, evt.button+1);
- });
- cOuter.addEventListener('mouseup', function (evt) {
- Input.onMouseUp(x, y, evt.button+1);
- });
- cOuter.addEventListener('contextmenu', function (evt) {
- if (Input.mouseTracksClicks()) {
- evt.preventDefault();
- }
- });
- cOuter.addEventListener('mousewheel', function (evt) {
- Input.onMouseWheel(x, y, evt.deltaY>0?1:-1);
- return false;
- });
- })();
+ /** Cursor alt choice for key message */
+ function ca(alt, normal) {
+ return opts.cu_alt ? alt : normal;
+ }
- /* End of line */
- if ((i > 0) && (i % W == 0)) {
- screenDiv.appendChild(mk('br'));
- }
- /* The cell */
- cOuter.appendChild(cInner);
- screenDiv.appendChild(cOuter);
+ /** Numpad alt choice for key message */
+ function na(alt, normal) {
+ return opts.np_alt ? alt : normal;
+ }
- cell = {
- t: ' ',
- fg: 7,
- bg: 0, // the colors will be replaced immediately as we receive data (user won't see this)
- attrs: 0,
- elem: cOuter,
- slot: cInner,
- x: i % W,
- y: Math.floor(i / W),
- };
- screen.push(cell);
- _draw(cell);
- }
- }
+ function _bindFnKeys() {
+ var keymap = {
+ 'tab': '\x09',
+ 'backspace': '\x08',
+ 'enter': '\x0d',
+ 'ctrl+enter': '\x0a',
+ 'esc': '\x1b',
+ 'up': ca('\x1bOA', '\x1b[A'),
+ 'down': ca('\x1bOB', '\x1b[B'),
+ 'right': ca('\x1bOC', '\x1b[C'),
+ 'left': ca('\x1bOD', '\x1b[D'),
+ 'home': ca('\x1bOH', fa('\x1b[H', '\x1b[1~')),
+ 'insert': '\x1b[2~',
+ 'delete': '\x1b[3~',
+ 'end': ca('\x1bOF', fa('\x1b[F', '\x1b[4~')),
+ 'pageup': '\x1b[5~',
+ 'pagedown': '\x1b[6~',
+ 'f1': fa('\x1bOP', '\x1b[11~'),
+ 'f2': fa('\x1bOQ', '\x1b[12~'),
+ 'f3': fa('\x1bOR', '\x1b[13~'),
+ 'f4': fa('\x1bOS', '\x1b[14~'),
+ 'f5': '\x1b[15~', // note the disconnect
+ 'f6': '\x1b[17~',
+ 'f7': '\x1b[18~',
+ 'f8': '\x1b[19~',
+ 'f9': '\x1b[20~',
+ 'f10': '\x1b[21~', // note the disconnect
+ 'f11': '\x1b[23~',
+ 'f12': '\x1b[24~',
+ 'shift+f1': fa('\x1bO1;2P', '\x1b[25~'),
+ 'shift+f2': fa('\x1bO1;2Q', '\x1b[26~'), // note the disconnect
+ 'shift+f3': fa('\x1bO1;2R', '\x1b[28~'),
+ 'shift+f4': fa('\x1bO1;2S', '\x1b[29~'), // note the disconnect
+ 'shift+f5': fa('\x1b[15;2~', '\x1b[31~'),
+ 'shift+f6': fa('\x1b[17;2~', '\x1b[32~'),
+ 'shift+f7': fa('\x1b[18;2~', '\x1b[33~'),
+ 'shift+f8': fa('\x1b[19;2~', '\x1b[34~'),
+ 'shift+f9': fa('\x1b[20;2~', '\x1b[35~'), // 35-38 are not standard - but what is?
+ 'shift+f10': fa('\x1b[21;2~', '\x1b[36~'),
+ 'shift+f11': fa('\x1b[22;2~', '\x1b[37~'),
+ 'shift+f12': fa('\x1b[23;2~', '\x1b[38~'),
+ 'np_0': na('\x1bOp', '0'),
+ 'np_1': na('\x1bOq', '1'),
+ 'np_2': na('\x1bOr', '2'),
+ 'np_3': na('\x1bOs', '3'),
+ 'np_4': na('\x1bOt', '4'),
+ 'np_5': na('\x1bOu', '5'),
+ 'np_6': na('\x1bOv', '6'),
+ 'np_7': na('\x1bOw', '7'),
+ 'np_8': na('\x1bOx', '8'),
+ 'np_9': na('\x1bOy', '9'),
+ 'np_mul': na('\x1bOR', '*'),
+ 'np_add': na('\x1bOl', '+'),
+ 'np_sub': na('\x1bOS', '-'),
+ 'np_point': na('\x1bOn', '.'),
+ 'np_div': na('\x1bOQ', '/'),
+ // we don't implement numlock key (should change in numpad_alt mode, but it's even more useless than the rest)
+ };
- /** Init the terminal */
- function _init() {
- /* Cursor blinking */
- clearInterval(blinkIval);
- blinkIval = setInterval(function () {
- cursor.a = !cursor.a;
- if (cursor.hidden || cursor.hanging) {
- cursor.a = false;
+ for (var k in keymap) {
+ if (keymap.hasOwnProperty(k)) {
+ bind(k, keymap[k]);
}
+ }
+ }
- if (!cursor.suppress) {
- _draw(_curCell(), cursor.forceOn || cursor.a);
- }
- }, 500);
+ /** Bind a keystroke to message */
+ function bind(combo, str) {
+ // mac fix - allow also cmd
+ if (combo.indexOf('ctrl+') !== -1) {
+ combo += ',' + combo.replace('ctrl', 'command');
+ }
- /* blink attribute animation */
- setInterval(function () {
- $('#screen').removeClass('blink-hide');
- setTimeout(function () {
- $('#screen').addClass('blink-hide');
- }, 800); // 200 ms ON
- }, 1000);
+ // unbind possible old binding
+ key.unbind(combo);
- inited = true;
+ key(combo, function (e) {
+ if (opts.no_keys) return;
+ e.preventDefault();
+ sendStrMsg(str)
+ });
}
- // constants for decoding the update blob
- var SEQ_SET_COLOR_ATTR = 1;
- var SEQ_REPEAT = 2;
- var SEQ_SET_COLOR = 3;
- var SEQ_SET_ATTR = 4;
+ /** Bind/rebind key messages */
+ function _initKeys() {
+ // This takes care of text characters typed
+ window.addEventListener('keypress', function(evt) {
+ if (opts.no_keys) return;
+ var str = '';
+ if (evt.key) str = evt.key;
+ else if (evt.which) str = String.fromCodePoint(evt.which);
+ if (str.length>0 && str.charCodeAt(0) >= 32) {
+// console.log("Typed ", str);
+ sendStrMsg(str);
+ }
+ });
- /** Parse received screen update object (leading S removed already) */
- function _load_content(str) {
- var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell;
+ // ctrl-letter codes are sent as simple low ASCII codes
+ for (var i = 1; i<=26;i++) {
+ bind('ctrl+' + String.fromCharCode(96+i), String.fromCharCode(i));
+ }
+ bind('ctrl+]', '\x1b'); // alternate way to enter ESC
+ bind('ctrl+\\', '\x1c');
+ bind('ctrl+[', '\x1d');
+ bind('ctrl+^', '\x1e');
+ bind('ctrl+_', '\x1f');
- if (!inited) _init();
+ _bindFnKeys();
+ }
- var cursorMoved;
+ // mouse button states
+ var mb1 = 0;
+ var mb2 = 0;
+ var mb3 = 0;
- // Set size
- num = parse2B(str, i); i += 2; // height
- num2 = parse2B(str, i); i += 2; // width
- if (num != H || num2 != W) {
- _rebuild(num, num2);
- }
- // console.log("Size ",num, num2);
+ /** Init the Input module */
+ function init() {
+ _initKeys();
- // Cursor position
- num = parse2B(str, i); i += 2; // row
- num2 = parse2B(str, i); i += 2; // col
- cursorMoved = (cursor.x != num2 || cursor.y != num);
- cursorSet(num, num2);
- // console.log("Cursor at ",num, num2);
+ // Button presses
+ qsa('#action-buttons button').forEach(function(s) {
+ s.addEventListener('click', function() {
+ sendBtnMsg(+this.dataset['n']);
+ });
+ });
- // Attributes
- num = parse2B(str, i); i += 2; // fg bg attribs
- cursor.hidden = !(num & (1<<0)); // DEC opt "visible"
- cursor.hanging = !!(num & (1<<1));
- // console.log("Attributes word ",num.toString(16)+'h');
+ // global mouse state tracking - for motion reporting
+ window.addEventListener('mousedown', function(evt) {
+ if (evt.button == 0) mb1 = 1;
+ if (evt.button == 1) mb2 = 1;
+ if (evt.button == 2) mb3 = 1;
+ });
- Input.setAlts(
- !!(num & (1<<2)), // cursors alt
- !!(num & (1<<3)), // numpad alt
- !!(num & (1<<4)) // fn keys alt
- );
+ window.addEventListener('mouseup', function(evt) {
+ if (evt.button == 0) mb1 = 0;
+ if (evt.button == 1) mb2 = 0;
+ if (evt.button == 2) mb3 = 0;
+ });
+ }
- var mt_click = !!(num & (1<<5));
- var mt_move = !!(num & (1<<6));
- Input.setMouseMode(
- mt_click,
- mt_move
- );
- $('#screen').toggleClass('noselect', mt_move);
+ /** Prepare modifiers byte for mouse message */
+ function packModifiersForMouse() {
+ return (key.isModifier('ctrl')?1:0) |
+ (key.isModifier('shift')?2:0) |
+ (key.isModifier('alt')?4:0) |
+ (key.isModifier('meta')?8:0);
+ }
- var show_buttons = !!(num & (1<<7));
- var show_config_links = !!(num & (1<<8));
- $('.x-term-conf-btn').toggleClass('hidden', !show_config_links);
- $('#action-buttons').toggleClass('hidden', !show_buttons);
+ return {
+ /** Init the Input module */
+ init: init,
- fg = 7;
- bg = 0;
- attrs = 0;
+ /** Send a literal string message */
+ sendString: sendStrMsg,
- // 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;
- }
- 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;
- // console.log("Repeat x ",num);
- for (; num>0 && ci 3 || b < 1) return;
+ var m = packModifiersForMouse();
+ Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
+ // console.log("B ",b," M ",m);
+ },
+ onMouseUp: function (x, y, b) {
+ if (!opts.mt_click) return;
+ if (b > 3 || b < 1) return;
+ var m = packModifiersForMouse();
+ Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
+ // console.log("B ",b," M ",m);
+ },
+ onMouseWheel: function (x, y, dir) {
+ if (!opts.mt_click) return;
+ // -1 ... btn 4 (away from user)
+ // +1 ... btn 5 (towards user)
+ var m = packModifiersForMouse();
+ var b = (dir < 0 ? 4 : 5);
+ Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
+ // console.log("B ",b," M ",m);
+ },
+ mouseTracksClicks: function() {
+ return opts.mt_click;
+ },
+ blockKeys: function(yes) {
+ opts.no_keys = yes;
}
+ };
+})();
- _drawAll();
+var Screen = (function () {
+ var W = 0, H = 0; // dimensions
+ var inited = false;
- // if (!cursor.hidden || cursor.hanging || !cursor.suppress) {
- // // hide cursor asap
- // _draw(_curCell(), false);
- // }
+ var cursor = {
+ a: false, // active (blink state)
+ x: 0, // 0-based coordinates
+ y: 0,
+ fg: 7, // colors 0-15
+ bg: 0,
+ attrs: 0,
+ suppress: false, // do not turn on in blink interval (for safe moving)
+ forceOn: false, // force on unless hanging: used to keep cursor visible during move
+ hidden: false, // do not show (DEC opt)
+ hanging: false, // cursor at column "W+1" - not visible
+ };
- if (cursorMoved) {
- cursor.forceOn = true;
- cursorFlashStartIval = setTimeout(function() {
- cursor.forceOn = false;
- }, 1200);
- _draw(_curCell(), true);
- }
- }
+ var screen = [];
+ var blinkIval;
+ var cursorFlashStartIval;
- /** Apply labels to buttons and screen title (leading T removed already) */
- function _load_labels(str) {
- var pieces = str.split('\x01');
- qs('h1').textContent = pieces[0];
- qsa('#action-buttons button').forEach(function(x, i) {
- var s = pieces[i+1].trim();
- // if empty string, use the "dim" effect and put nbsp instead to stretch the btn vertically
- x.innerHTML = s.length > 0 ? e(s) : " ";
- x.style.opacity = s.length > 0 ? 1 : 0.2;
- });
- }
+ // Some non-bold Fraktur symbols are outside the contiguous block
+ var frakturExceptions = {
+ 'C': '\u212d',
+ 'H': '\u210c',
+ 'I': '\u2111',
+ 'R': '\u211c',
+ 'Z': '\u2128',
+ };
- /** Audible beep for ASCII 7 */
- function _beep() {
- var osc, gain;
- if (!audioCtx) return;
+ // for BEL
+ var audioCtx = null;
+ try {
+ audioCtx = new (window.AudioContext || window.audioContext || window.webkitAudioContext)();
+ } catch (er) {
+ console.error("No AudioContext!", er);
+ }
- // Main beep
- osc = audioCtx.createOscillator();
- gain = audioCtx.createGain();
- osc.connect(gain);
- gain.connect(audioCtx.destination);
- gain.gain.value = 0.5;
- osc.frequency.value = 750;
- osc.type = 'sine';
- osc.start();
- osc.stop(audioCtx.currentTime+0.05);
+ /** Get cell under cursor */
+ function _curCell() {
+ return screen[cursor.y*W + cursor.x];
+ }
- // Surrogate beep (making it sound like 'oops')
- osc = audioCtx.createOscillator();
- gain = audioCtx.createGain();
- osc.connect(gain);
- gain.connect(audioCtx.destination);
- gain.gain.value = 0.2;
- osc.frequency.value = 400;
- osc.type = 'sine';
- osc.start(audioCtx.currentTime+0.05);
- osc.stop(audioCtx.currentTime+0.08);
+ /** 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());
}
- /** Load screen content from a binary sequence (new) */
- function load(str) {
- var content = str.substr(1);
- switch(str.charAt(0)) {
- case 'S':
- _load_content(content);
- break;
- case 'T':
- _load_labels(content);
- break;
- case 'B':
- _beep();
- break;
- default:
- console.warn("Bad data message type, ignoring.");
- console.log(str);
+ function alpha2fraktur(t) {
+ // 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));
+ }
}
+ return t;
}
- return {
- load: load, // full load (string)
- };
-})();
+ /** Update cell on display. inv = invert (for cursor) */
+ function _draw(cell, inv) {
+ if (!cell) return;
+ if (typeof inv == 'undefined') {
+ inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y;
+ }
-/** Handle connections */
-var Conn = (function() {
- var ws;
- var heartbeatTout;
- var pingIv;
+ var fg, bg, cn, t;
- function onOpen(evt) {
- console.log("CONNECTED");
+ fg = inv ? cell.bg : cell.fg;
+ bg = inv ? cell.fg : cell.bg;
+
+ t = cell.t;
+ if (!t.length) t = ' ';
+
+ cn = 'fg' + fg + ' bg' + bg;
+ if (cell.attrs & (1<<0)) cn += ' bold';
+ if (cell.attrs & (1<<1)) cn += ' faint';
+ 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';
+ t = alpha2fraktur(t);
+ }
+ if (cell.attrs & (1<<6)) cn += ' strike';
+
+ cell.slot.textContent = t;
+ cell.elem.className = cn;
}
- function onClose(evt) {
- console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
- setTimeout(function() {
- init();
- }, 200);
- // this happens when the buffer gets fucked up via invalid unicode.
- // we basically use polling instead of socket then
+ /** Show entire screen */
+ function _drawAll() {
+ for (var i = W*H-1; i>=0; i--) {
+ _draw(screen[i]);
+ }
}
- function onMessage(evt) {
- try {
- // . = heartbeat
- if (evt.data != '.') {
- //console.log("RX: ", evt.data);
- // Assume all our messages are screen updates
- Screen.load(evt.data);
+ function _rebuild(rows, cols) {
+ W = cols;
+ H = rows;
+
+ /* Build screen & show */
+ var cOuter, cInner, cell, screenDiv = qs('#screen');
+
+ // Empty the screen node
+ while (screenDiv.firstChild) screenDiv.removeChild(screenDiv.firstChild);
+
+ screen = [];
+
+ for(var i = 0; i < W*H; i++) {
+ cOuter = mk('span');
+ cInner = mk('span');
+
+ /* Mouse tracking */
+ (function() {
+ var x = i % W;
+ var y = Math.floor(i / W);
+ cOuter.addEventListener('mouseenter', function (evt) {
+ Input.onMouseMove(x, y);
+ });
+ cOuter.addEventListener('mousedown', function (evt) {
+ Input.onMouseDown(x, y, evt.button+1);
+ });
+ cOuter.addEventListener('mouseup', function (evt) {
+ Input.onMouseUp(x, y, evt.button+1);
+ });
+ cOuter.addEventListener('contextmenu', function (evt) {
+ if (Input.mouseTracksClicks()) {
+ evt.preventDefault();
+ }
+ });
+ cOuter.addEventListener('mousewheel', function (evt) {
+ Input.onMouseWheel(x, y, evt.deltaY>0?1:-1);
+ return false;
+ });
+ })();
+
+ /* End of line */
+ if ((i > 0) && (i % W == 0)) {
+ screenDiv.appendChild(mk('br'));
}
- heartbeat();
- } catch(e) {
- console.error(e);
- }
- }
-
- function doSend(message) {
- //console.log("TX: ", message);
+ /* The cell */
+ cOuter.appendChild(cInner);
+ screenDiv.appendChild(cOuter);
- if (!ws) return false; // for dry testing
- if (ws.readyState != 1) {
- console.error("Socket not ready");
- return false;
- }
- if (typeof message != "string") {
- message = JSON.stringify(message);
+ cell = {
+ t: ' ',
+ fg: 7,
+ bg: 0, // the colors will be replaced immediately as we receive data (user won't see this)
+ attrs: 0,
+ elem: cOuter,
+ slot: cInner,
+ x: i % W,
+ y: Math.floor(i / W),
+ };
+ screen.push(cell);
+ _draw(cell);
}
- ws.send(message);
- return true;
}
- function init() {
- heartbeat();
-
- ws = new WebSocket("ws://"+_root+"/term/update.ws");
- ws.onopen = onOpen;
- ws.onclose = onClose;
- ws.onmessage = onMessage;
+ /** Init the terminal */
+ function _init() {
+ /* Cursor blinking */
+ clearInterval(blinkIval);
+ blinkIval = setInterval(function () {
+ cursor.a = !cursor.a;
+ if (cursor.hidden || cursor.hanging) {
+ cursor.a = false;
+ }
- console.log("Opening socket.");
+ if (!cursor.suppress) {
+ _draw(_curCell(), cursor.forceOn || cursor.a);
+ }
+ }, 500);
- // Ask for initial data
- $.get('http://'+_root+'/term/init', function(resp, status) {
- if (status !== 200) location.reload(true);
- console.log("Data received!");
- Screen.load(resp);
- heartbeat();
+ /* blink attribute animation */
+ setInterval(function () {
+ $('#screen').removeClass('blink-hide');
+ setTimeout(function () {
+ $('#screen').addClass('blink-hide');
+ }, 800); // 200 ms ON
+ }, 1000);
- showPage();
- });
+ inited = true;
}
- function heartbeat() {
- clearTimeout(heartbeatTout);
- heartbeatTout = setTimeout(heartbeatFail, 2000);
- }
+ // constants for decoding the update blob
+ var SEQ_SET_COLOR_ATTR = 1;
+ var SEQ_REPEAT = 2;
+ var SEQ_SET_COLOR = 3;
+ var SEQ_SET_ATTR = 4;
- function heartbeatFail() {
- console.error("Heartbeat lost, probing server...");
- pingIv = setInterval(function() {
- console.log("> ping");
- $.get('http://'+_root+'/system/ping', function(resp, status) {
- if (status == 200) {
- clearInterval(pingIv);
- console.info("Server ready, reloading page...");
- location.reload();
- }
- }, {
- timeout: 100,
- });
- }, 500);
- }
+ /** Parse received screen update object (leading S removed already) */
+ function _load_content(str) {
+ var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell;
- return {
- ws: null,
- init: init,
- send: doSend
- };
-})();
+ if (!inited) _init();
-/**
- * User input
- *
- * --- Rx messages: ---
- * S - screen content (binary encoding of the entire screen with simple compression)
- * T - text labels - Title and buttons, \0x01-separated
- * B - beep
- * . - heartbeat
- *
- * --- Tx messages ---
- * s - string
- * b - action button
- * p - mb press
- * r - mb release
- * m - mouse move
- */
-var Input = (function() {
- var opts = {
- np_alt: false,
- cu_alt: false,
- fn_alt: false,
- mt_click: false,
- mt_move: false,
- no_keys: false,
- };
+ var cursorMoved;
- /** Send a literal message */
- function sendStrMsg(str) {
- return Conn.send("s"+str);
- }
+ // Set size
+ num = parse2B(str, i); i += 2; // height
+ num2 = parse2B(str, i); i += 2; // width
+ if (num != H || num2 != W) {
+ _rebuild(num, num2);
+ }
+ // console.log("Size ",num, num2);
- /** Send a button event */
- function sendBtnMsg(n) {
- Conn.send("b"+Chr(n));
- }
+ // Cursor position
+ num = parse2B(str, i); i += 2; // row
+ num2 = parse2B(str, i); i += 2; // col
+ cursorMoved = (cursor.x != num2 || cursor.y != num);
+ cursorSet(num, num2);
+ // console.log("Cursor at ",num, num2);
- /** Fn alt choice for key message */
- function fa(alt, normal) {
- return opts.fn_alt ? alt : normal;
- }
+ // Attributes
+ num = parse2B(str, i); i += 2; // fg bg attribs
+ cursor.hidden = !(num & (1<<0)); // DEC opt "visible"
+ cursor.hanging = !!(num & (1<<1));
+ // console.log("Attributes word ",num.toString(16)+'h');
- /** Cursor alt choice for key message */
- function ca(alt, normal) {
- return opts.cu_alt ? alt : normal;
- }
+ Input.setAlts(
+ !!(num & (1<<2)), // cursors alt
+ !!(num & (1<<3)), // numpad alt
+ !!(num & (1<<4)) // fn keys alt
+ );
- /** Numpad alt choice for key message */
- function na(alt, normal) {
- return opts.np_alt ? alt : normal;
- }
+ var mt_click = !!(num & (1<<5));
+ var mt_move = !!(num & (1<<6));
+ Input.setMouseMode(
+ mt_click,
+ mt_move
+ );
+ $('#screen').toggleClass('noselect', mt_move);
- function _bindFnKeys() {
- var keymap = {
- 'tab': '\x09',
- 'backspace': '\x08',
- 'enter': '\x0d',
- 'ctrl+enter': '\x0a',
- 'esc': '\x1b',
- 'up': ca('\x1bOA', '\x1b[A'),
- 'down': ca('\x1bOB', '\x1b[B'),
- 'right': ca('\x1bOC', '\x1b[C'),
- 'left': ca('\x1bOD', '\x1b[D'),
- 'home': ca('\x1bOH', fa('\x1b[H', '\x1b[1~')),
- 'insert': '\x1b[2~',
- 'delete': '\x1b[3~',
- 'end': ca('\x1bOF', fa('\x1b[F', '\x1b[4~')),
- 'pageup': '\x1b[5~',
- 'pagedown': '\x1b[6~',
- 'f1': fa('\x1bOP', '\x1b[11~'),
- 'f2': fa('\x1bOQ', '\x1b[12~'),
- 'f3': fa('\x1bOR', '\x1b[13~'),
- 'f4': fa('\x1bOS', '\x1b[14~'),
- 'f5': '\x1b[15~', // note the disconnect
- 'f6': '\x1b[17~',
- 'f7': '\x1b[18~',
- 'f8': '\x1b[19~',
- 'f9': '\x1b[20~',
- 'f10': '\x1b[21~', // note the disconnect
- 'f11': '\x1b[23~',
- 'f12': '\x1b[24~',
- 'shift+f1': fa('\x1bO1;2P', '\x1b[25~'),
- 'shift+f2': fa('\x1bO1;2Q', '\x1b[26~'), // note the disconnect
- 'shift+f3': fa('\x1bO1;2R', '\x1b[28~'),
- 'shift+f4': fa('\x1bO1;2S', '\x1b[29~'), // note the disconnect
- 'shift+f5': fa('\x1b[15;2~', '\x1b[31~'),
- 'shift+f6': fa('\x1b[17;2~', '\x1b[32~'),
- 'shift+f7': fa('\x1b[18;2~', '\x1b[33~'),
- 'shift+f8': fa('\x1b[19;2~', '\x1b[34~'),
- 'shift+f9': fa('\x1b[20;2~', '\x1b[35~'), // 35-38 are not standard - but what is?
- 'shift+f10': fa('\x1b[21;2~', '\x1b[36~'),
- 'shift+f11': fa('\x1b[22;2~', '\x1b[37~'),
- 'shift+f12': fa('\x1b[23;2~', '\x1b[38~'),
- 'np_0': na('\x1bOp', '0'),
- 'np_1': na('\x1bOq', '1'),
- 'np_2': na('\x1bOr', '2'),
- 'np_3': na('\x1bOs', '3'),
- 'np_4': na('\x1bOt', '4'),
- 'np_5': na('\x1bOu', '5'),
- 'np_6': na('\x1bOv', '6'),
- 'np_7': na('\x1bOw', '7'),
- 'np_8': na('\x1bOx', '8'),
- 'np_9': na('\x1bOy', '9'),
- 'np_mul': na('\x1bOR', '*'),
- 'np_add': na('\x1bOl', '+'),
- 'np_sub': na('\x1bOS', '-'),
- 'np_point': na('\x1bOn', '.'),
- 'np_div': na('\x1bOQ', '/'),
- // we don't implement numlock key (should change in numpad_alt mode, but it's even more useless than the rest)
- };
+ var show_buttons = !!(num & (1<<7));
+ var show_config_links = !!(num & (1<<8));
+ $('.x-term-conf-btn').toggleClass('hidden', !show_config_links);
+ $('#action-buttons').toggleClass('hidden', !show_buttons);
+
+ fg = 7;
+ bg = 0;
+ attrs = 0;
- for (var k in keymap) {
- if (keymap.hasOwnProperty(k)) {
- bind(k, keymap[k]);
+ // 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;
+ }
+ 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;
+ // console.log("Repeat x ",num);
+ for (; num>0 && ci0 && str.charCodeAt(0) >= 32) {
-// console.log("Typed ", str);
- sendStrMsg(str);
- }
+ /** Apply labels to buttons and screen title (leading T removed already) */
+ function _load_labels(str) {
+ var pieces = str.split('\x01');
+ qs('h1').textContent = pieces[0];
+ qsa('#action-buttons button').forEach(function(x, i) {
+ var s = pieces[i+1].trim();
+ // if empty string, use the "dim" effect and put nbsp instead to stretch the btn vertically
+ x.innerHTML = s.length > 0 ? e(s) : " ";
+ x.style.opacity = s.length > 0 ? 1 : 0.2;
});
-
- // ctrl-letter codes are sent as simple low ASCII codes
- for (var i = 1; i<=26;i++) {
- bind('ctrl+' + String.fromCharCode(96+i), String.fromCharCode(i));
- }
- bind('ctrl+]', '\x1b'); // alternate way to enter ESC
- bind('ctrl+\\', '\x1c');
- bind('ctrl+[', '\x1d');
- bind('ctrl+^', '\x1e');
- bind('ctrl+_', '\x1f');
-
- _bindFnKeys();
}
- // mouse button states
- var mb1 = 0;
- var mb2 = 0;
- var mb3 = 0;
-
- /** Init the Input module */
- function init() {
- _initKeys();
-
- // Button presses
- qsa('#action-buttons button').forEach(function(s) {
- s.addEventListener('click', function() {
- sendBtnMsg(+this.dataset['n']);
- });
- });
+ /** Audible beep for ASCII 7 */
+ function _beep() {
+ var osc, gain;
+ if (!audioCtx) return;
- // global mouse state tracking - for motion reporting
- window.addEventListener('mousedown', function(evt) {
- if (evt.button == 0) mb1 = 1;
- if (evt.button == 1) mb2 = 1;
- if (evt.button == 2) mb3 = 1;
- });
+ // Main beep
+ osc = audioCtx.createOscillator();
+ gain = audioCtx.createGain();
+ osc.connect(gain);
+ gain.connect(audioCtx.destination);
+ gain.gain.value = 0.5;
+ osc.frequency.value = 750;
+ osc.type = 'sine';
+ osc.start();
+ osc.stop(audioCtx.currentTime+0.05);
- window.addEventListener('mouseup', function(evt) {
- if (evt.button == 0) mb1 = 0;
- if (evt.button == 1) mb2 = 0;
- if (evt.button == 2) mb3 = 0;
- });
+ // Surrogate beep (making it sound like 'oops')
+ osc = audioCtx.createOscillator();
+ gain = audioCtx.createGain();
+ osc.connect(gain);
+ gain.connect(audioCtx.destination);
+ gain.gain.value = 0.2;
+ osc.frequency.value = 400;
+ osc.type = 'sine';
+ osc.start(audioCtx.currentTime+0.05);
+ osc.stop(audioCtx.currentTime+0.08);
}
- /** Prepare modifiers byte for mouse message */
- function packModifiersForMouse() {
- return (key.isModifier('ctrl')?1:0) |
- (key.isModifier('shift')?2:0) |
- (key.isModifier('alt')?4:0) |
- (key.isModifier('meta')?8:0);
+ /** Load screen content from a binary sequence (new) */
+ function load(str) {
+ var content = str.substr(1);
+ switch(str.charAt(0)) {
+ case 'S':
+ _load_content(content);
+ break;
+ case 'T':
+ _load_labels(content);
+ break;
+ case 'B':
+ _beep();
+ break;
+ default:
+ console.warn("Bad data message type, ignoring.");
+ console.log(str);
+ }
}
- return {
- /** Init the Input module */
- init: init,
-
- /** Send a literal string message */
- sendString: sendStrMsg,
-
- /** Enable alternate key modes (cursors, numpad, fn) */
- setAlts: function(cu, np, fn) {
- if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn) {
- opts.cu_alt = cu;
- opts.np_alt = np;
- opts.fn_alt = fn;
-
- // rebind keys - codes have changed
- _bindFnKeys();
- }
- },
-
- setMouseMode: function(click, move) {
- opts.mt_click = click;
- opts.mt_move = move;
- },
-
- // Mouse events
- onMouseMove: function (x, y) {
- if (!opts.mt_move) return;
- var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0;
- var m = packModifiersForMouse();
- Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
- },
- onMouseDown: function (x, y, b) {
- if (!opts.mt_click) return;
- if (b > 3 || b < 1) return;
- var m = packModifiersForMouse();
- Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
- // console.log("B ",b," M ",m);
- },
- onMouseUp: function (x, y, b) {
- if (!opts.mt_click) return;
- if (b > 3 || b < 1) return;
- var m = packModifiersForMouse();
- Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
- // console.log("B ",b," M ",m);
- },
- onMouseWheel: function (x, y, dir) {
- if (!opts.mt_click) return;
- // -1 ... btn 4 (away from user)
- // +1 ... btn 5 (towards user)
- var m = packModifiersForMouse();
- var b = (dir < 0 ? 4 : 5);
- Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
- // console.log("B ",b," M ",m);
- },
- mouseTracksClicks: function() {
- return opts.mt_click;
- },
- blockKeys: function(yes) {
- opts.no_keys = yes;
- }
+ return {
+ load: load, // full load (string)
};
})();
-
-
/** File upload utility */
var TermUpl = (function() {
- var fuLines, fuPos, fuTout, fuDelay, fuNL;
+ var lines, // array of lines without newlines
+ line_i, // current line index
+ fuTout, // timeout handle for line sending
+ send_delay_ms, // delay between lines (ms)
+ nl_str, // newline string to use
+ curLine, // current line (when using fuOil)
+ inline_pos; // Offset in line (for long lines)
+
+ // lines longer than this are split to chunks
+ // sending a super-ling string through the socket is not a good idea
+ var MAX_LINE_LEN = 128;
function fuOpen() {
fuStatus("Ready...");
@@ -2356,7 +2389,7 @@ var TermUpl = (function() {
function onClose() {
console.log("Upload modal closed.");
clearTimeout(fuTout);
- fuPos = 0;
+ line_i = 0;
Input.blockKeys(false);
}
@@ -2371,10 +2404,18 @@ var TermUpl = (function() {
return;
}
- fuLines = v.split('\n');
- fuPos = 0;
- fuDelay = qs('#fu_delay').value;
- fuNL = {
+ lines = v.split('\n');
+ line_i = 0;
+ inline_pos = 0; // offset in line
+ send_delay_ms = qs('#fu_delay').value;
+
+ // sanitize - 0 causes overflows
+ if (send_delay_ms <= 0) {
+ send_delay_ms = 1;
+ qs('#fu_delay').value = 1;
+ }
+
+ nl_str = {
'CR': '\r',
'LF': '\n',
'CRLF': '\r\n',
@@ -2391,19 +2432,52 @@ var TermUpl = (function() {
return;
}
- if (!Input.sendString(fuLines[fuPos++] + fuNL)) {
+ if (!Conn.canSend()) {
+ // postpone
+ fuTout = setTimeout(fuSendLine, 1);
+ return;
+ }
+
+ if (inline_pos == 0) {
+ curLine = lines[line_i++] + nl_str;
+ }
+
+ var chunk;
+ if ((curLine.length - inline_pos) <= MAX_LINE_LEN) {
+ chunk = curLine.substr(inline_pos, MAX_LINE_LEN);
+ inline_pos = 0;
+ } else {
+ chunk = curLine.substr(inline_pos, MAX_LINE_LEN);
+ inline_pos += MAX_LINE_LEN;
+ }
+
+ console.log("-> " + chunk);
+ if (!Input.sendString(chunk)) {
fuStatus("FAILED!");
return;
}
- var all = fuLines.length;
+ var all = lines.length;
- fuStatus(fuPos+" / "+all+ " ("+(Math.round((fuPos/all)*1000)/10)+"%)");
+ fuStatus(line_i+" / "+all+ " ("+(Math.round((line_i/all)*1000)/10)+"%)");
- if (fuLines.length > fuPos) {
- setTimeout(fuSendLine, fuDelay);
+ if (lines.length > line_i || inline_pos > 0) {
+ fuTout = setTimeout(fuSendLine, send_delay_ms);
} else {
- fuClose();
+ closeWhenReady();
+ }
+ }
+
+ function closeWhenReady() {
+ if (!Conn.canSend()) {
+ fuStatus("Waiting for Tx buffer...");
+ setTimeout(closeWhenReady, 250);
+ } else {
+ fuStatus("Done.");
+ // delay to show it
+ setTimeout(function() {
+ fuClose();
+ }, 250);
}
}
@@ -2437,7 +2511,6 @@ var TermUpl = (function() {
open: fuOpen,
}
})();
-
/** Init the terminal sub-module - called from HTML */
window.termInit = function () {
Conn.init();
diff --git a/html_orig/jssrc/term.js b/html_orig/jssrc/term.js
index 80d45da..2211c60 100644
--- a/html_orig/jssrc/term.js
+++ b/html_orig/jssrc/term.js
@@ -1,837 +1,3 @@
-var Screen = (function () {
- var W = 0, H = 0; // 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,
- attrs: 0,
- suppress: false, // do not turn on in blink interval (for safe moving)
- forceOn: false, // force on unless hanging: used to keep cursor visible during move
- hidden: false, // do not show (DEC opt)
- hanging: false, // cursor at column "W+1" - not visible
- };
-
- var screen = [];
- var blinkIval;
- var cursorFlashStartIval;
-
- // Some non-bold Fraktur symbols are outside the contiguous block
- var frakturExceptions = {
- 'C': '\u212d',
- 'H': '\u210c',
- 'I': '\u2111',
- 'R': '\u211c',
- 'Z': '\u2128',
- };
-
- // for BEL
- var audioCtx = null;
- try {
- audioCtx = new (window.AudioContext || window.audioContext || window.webkitAudioContext)();
- } catch (er) {
- console.error("No AudioContext!", er);
- }
-
- /** 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;
- _draw(_curCell(), false);
- cursor.x = x;
- cursor.y = y;
- // Show again
- cursor.suppress = false;
- _draw(_curCell());
- }
-
- function alpha2fraktur(t) {
- // 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));
- }
- }
- return t;
- }
-
- /** Update cell on display. inv = invert (for cursor) */
- function _draw(cell, inv) {
- if (!cell) return;
- if (typeof inv == 'undefined') {
- inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y;
- }
-
- var fg, bg, cn, t;
-
- fg = inv ? cell.bg : cell.fg;
- bg = inv ? cell.fg : cell.bg;
-
- t = cell.t;
- if (!t.length) t = ' ';
-
- cn = 'fg' + fg + ' bg' + bg;
- if (cell.attrs & (1<<0)) cn += ' bold';
- if (cell.attrs & (1<<1)) cn += ' faint';
- 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';
- t = alpha2fraktur(t);
- }
- if (cell.attrs & (1<<6)) cn += ' strike';
-
- cell.slot.textContent = t;
- cell.elem.className = cn;
- }
-
- /** Show entire screen */
- function _drawAll() {
- for (var i = W*H-1; i>=0; i--) {
- _draw(screen[i]);
- }
- }
-
- function _rebuild(rows, cols) {
- W = cols;
- H = rows;
-
- /* Build screen & show */
- var cOuter, cInner, cell, screenDiv = qs('#screen');
-
- // Empty the screen node
- while (screenDiv.firstChild) screenDiv.removeChild(screenDiv.firstChild);
-
- screen = [];
-
- for(var i = 0; i < W*H; i++) {
- cOuter = mk('span');
- cInner = mk('span');
-
- /* Mouse tracking */
- (function() {
- var x = i % W;
- var y = Math.floor(i / W);
- cOuter.addEventListener('mouseenter', function (evt) {
- Input.onMouseMove(x, y);
- });
- cOuter.addEventListener('mousedown', function (evt) {
- Input.onMouseDown(x, y, evt.button+1);
- });
- cOuter.addEventListener('mouseup', function (evt) {
- Input.onMouseUp(x, y, evt.button+1);
- });
- cOuter.addEventListener('contextmenu', function (evt) {
- if (Input.mouseTracksClicks()) {
- evt.preventDefault();
- }
- });
- cOuter.addEventListener('mousewheel', function (evt) {
- Input.onMouseWheel(x, y, evt.deltaY>0?1:-1);
- return false;
- });
- })();
-
- /* End of line */
- if ((i > 0) && (i % W == 0)) {
- screenDiv.appendChild(mk('br'));
- }
- /* The cell */
- cOuter.appendChild(cInner);
- screenDiv.appendChild(cOuter);
-
- cell = {
- t: ' ',
- fg: 7,
- bg: 0, // the colors will be replaced immediately as we receive data (user won't see this)
- attrs: 0,
- elem: cOuter,
- slot: cInner,
- x: i % W,
- y: Math.floor(i / W),
- };
- screen.push(cell);
- _draw(cell);
- }
- }
-
- /** Init the terminal */
- function _init() {
- /* Cursor blinking */
- clearInterval(blinkIval);
- blinkIval = setInterval(function () {
- cursor.a = !cursor.a;
- if (cursor.hidden || cursor.hanging) {
- cursor.a = false;
- }
-
- if (!cursor.suppress) {
- _draw(_curCell(), cursor.forceOn || cursor.a);
- }
- }, 500);
-
- /* blink attribute animation */
- setInterval(function () {
- $('#screen').removeClass('blink-hide');
- setTimeout(function () {
- $('#screen').addClass('blink-hide');
- }, 800); // 200 ms ON
- }, 1000);
-
- inited = true;
- }
-
- // constants for decoding the update blob
- var SEQ_SET_COLOR_ATTR = 1;
- var SEQ_REPEAT = 2;
- var SEQ_SET_COLOR = 3;
- var SEQ_SET_ATTR = 4;
-
- /** Parse received screen update object (leading S removed already) */
- function _load_content(str) {
- var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell;
-
- if (!inited) _init();
-
- var cursorMoved;
-
- // Set size
- num = parse2B(str, i); i += 2; // height
- num2 = parse2B(str, i); i += 2; // width
- if (num != H || num2 != W) {
- _rebuild(num, num2);
- }
- // console.log("Size ",num, num2);
-
- // Cursor position
- num = parse2B(str, i); i += 2; // row
- num2 = parse2B(str, i); i += 2; // col
- cursorMoved = (cursor.x != num2 || cursor.y != num);
- cursorSet(num, num2);
- // console.log("Cursor at ",num, num2);
-
- // Attributes
- num = parse2B(str, i); i += 2; // fg bg attribs
- cursor.hidden = !(num & (1<<0)); // DEC opt "visible"
- cursor.hanging = !!(num & (1<<1));
- // console.log("Attributes word ",num.toString(16)+'h');
-
- Input.setAlts(
- !!(num & (1<<2)), // cursors alt
- !!(num & (1<<3)), // numpad alt
- !!(num & (1<<4)) // fn keys alt
- );
-
- var mt_click = !!(num & (1<<5));
- var mt_move = !!(num & (1<<6));
- Input.setMouseMode(
- mt_click,
- mt_move
- );
- $('#screen').toggleClass('noselect', mt_move);
-
- var show_buttons = !!(num & (1<<7));
- var show_config_links = !!(num & (1<<8));
- $('.x-term-conf-btn').toggleClass('hidden', !show_config_links);
- $('#action-buttons').toggleClass('hidden', !show_buttons);
-
- fg = 7;
- bg = 0;
- 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;
- }
- 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;
- // console.log("Repeat x ",num);
- for (; num>0 && ci 0 ? e(s) : " ";
- x.style.opacity = s.length > 0 ? 1 : 0.2;
- });
- }
-
- /** Audible beep for ASCII 7 */
- function _beep() {
- var osc, gain;
- if (!audioCtx) return;
-
- // Main beep
- osc = audioCtx.createOscillator();
- gain = audioCtx.createGain();
- osc.connect(gain);
- gain.connect(audioCtx.destination);
- gain.gain.value = 0.5;
- osc.frequency.value = 750;
- osc.type = 'sine';
- osc.start();
- osc.stop(audioCtx.currentTime+0.05);
-
- // Surrogate beep (making it sound like 'oops')
- osc = audioCtx.createOscillator();
- gain = audioCtx.createGain();
- osc.connect(gain);
- gain.connect(audioCtx.destination);
- gain.gain.value = 0.2;
- osc.frequency.value = 400;
- osc.type = 'sine';
- osc.start(audioCtx.currentTime+0.05);
- osc.stop(audioCtx.currentTime+0.08);
- }
-
- /** Load screen content from a binary sequence (new) */
- function load(str) {
- var content = str.substr(1);
- switch(str.charAt(0)) {
- case 'S':
- _load_content(content);
- break;
- case 'T':
- _load_labels(content);
- break;
- case 'B':
- _beep();
- break;
- default:
- console.warn("Bad data message type, ignoring.");
- console.log(str);
- }
- }
-
- return {
- load: load, // full load (string)
- };
-})();
-
-/** Handle connections */
-var Conn = (function() {
- var ws;
- var heartbeatTout;
- var pingIv;
-
- function onOpen(evt) {
- console.log("CONNECTED");
- }
-
- function onClose(evt) {
- console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
- setTimeout(function() {
- init();
- }, 200);
- // this happens when the buffer gets fucked up via invalid unicode.
- // we basically use polling instead of socket then
- }
-
- function onMessage(evt) {
- try {
- // . = heartbeat
- if (evt.data != '.') {
- //console.log("RX: ", evt.data);
- // Assume all our messages are screen updates
- Screen.load(evt.data);
- }
- heartbeat();
- } catch(e) {
- console.error(e);
- }
- }
-
- function doSend(message) {
- //console.log("TX: ", message);
-
- if (!ws) return false; // for dry testing
- if (ws.readyState != 1) {
- console.error("Socket not ready");
- return false;
- }
- if (typeof message != "string") {
- message = JSON.stringify(message);
- }
- ws.send(message);
- return true;
- }
-
- function init() {
- heartbeat();
-
- ws = new WebSocket("ws://"+_root+"/term/update.ws");
- ws.onopen = onOpen;
- ws.onclose = onClose;
- ws.onmessage = onMessage;
-
- console.log("Opening socket.");
-
- // Ask for initial data
- $.get('http://'+_root+'/term/init', function(resp, status) {
- if (status !== 200) location.reload(true);
- console.log("Data received!");
- Screen.load(resp);
- heartbeat();
-
- showPage();
- });
- }
-
- function heartbeat() {
- clearTimeout(heartbeatTout);
- heartbeatTout = setTimeout(heartbeatFail, 2000);
- }
-
- function heartbeatFail() {
- console.error("Heartbeat lost, probing server...");
- pingIv = setInterval(function() {
- console.log("> ping");
- $.get('http://'+_root+'/system/ping', function(resp, status) {
- if (status == 200) {
- clearInterval(pingIv);
- console.info("Server ready, reloading page...");
- location.reload();
- }
- }, {
- timeout: 100,
- });
- }, 500);
- }
-
- return {
- ws: null,
- init: init,
- send: doSend
- };
-})();
-
-/**
- * User input
- *
- * --- Rx messages: ---
- * S - screen content (binary encoding of the entire screen with simple compression)
- * T - text labels - Title and buttons, \0x01-separated
- * B - beep
- * . - heartbeat
- *
- * --- Tx messages ---
- * s - string
- * b - action button
- * p - mb press
- * r - mb release
- * m - mouse move
- */
-var Input = (function() {
- var opts = {
- np_alt: false,
- cu_alt: false,
- fn_alt: false,
- mt_click: false,
- mt_move: false,
- no_keys: false,
- };
-
- /** Send a literal message */
- function sendStrMsg(str) {
- return Conn.send("s"+str);
- }
-
- /** Send a button event */
- function sendBtnMsg(n) {
- Conn.send("b"+Chr(n));
- }
-
- /** Fn alt choice for key message */
- function fa(alt, normal) {
- return opts.fn_alt ? alt : normal;
- }
-
- /** Cursor alt choice for key message */
- function ca(alt, normal) {
- return opts.cu_alt ? alt : normal;
- }
-
- /** Numpad alt choice for key message */
- function na(alt, normal) {
- return opts.np_alt ? alt : normal;
- }
-
- function _bindFnKeys() {
- var keymap = {
- 'tab': '\x09',
- 'backspace': '\x08',
- 'enter': '\x0d',
- 'ctrl+enter': '\x0a',
- 'esc': '\x1b',
- 'up': ca('\x1bOA', '\x1b[A'),
- 'down': ca('\x1bOB', '\x1b[B'),
- 'right': ca('\x1bOC', '\x1b[C'),
- 'left': ca('\x1bOD', '\x1b[D'),
- 'home': ca('\x1bOH', fa('\x1b[H', '\x1b[1~')),
- 'insert': '\x1b[2~',
- 'delete': '\x1b[3~',
- 'end': ca('\x1bOF', fa('\x1b[F', '\x1b[4~')),
- 'pageup': '\x1b[5~',
- 'pagedown': '\x1b[6~',
- 'f1': fa('\x1bOP', '\x1b[11~'),
- 'f2': fa('\x1bOQ', '\x1b[12~'),
- 'f3': fa('\x1bOR', '\x1b[13~'),
- 'f4': fa('\x1bOS', '\x1b[14~'),
- 'f5': '\x1b[15~', // note the disconnect
- 'f6': '\x1b[17~',
- 'f7': '\x1b[18~',
- 'f8': '\x1b[19~',
- 'f9': '\x1b[20~',
- 'f10': '\x1b[21~', // note the disconnect
- 'f11': '\x1b[23~',
- 'f12': '\x1b[24~',
- 'shift+f1': fa('\x1bO1;2P', '\x1b[25~'),
- 'shift+f2': fa('\x1bO1;2Q', '\x1b[26~'), // note the disconnect
- 'shift+f3': fa('\x1bO1;2R', '\x1b[28~'),
- 'shift+f4': fa('\x1bO1;2S', '\x1b[29~'), // note the disconnect
- 'shift+f5': fa('\x1b[15;2~', '\x1b[31~'),
- 'shift+f6': fa('\x1b[17;2~', '\x1b[32~'),
- 'shift+f7': fa('\x1b[18;2~', '\x1b[33~'),
- 'shift+f8': fa('\x1b[19;2~', '\x1b[34~'),
- 'shift+f9': fa('\x1b[20;2~', '\x1b[35~'), // 35-38 are not standard - but what is?
- 'shift+f10': fa('\x1b[21;2~', '\x1b[36~'),
- 'shift+f11': fa('\x1b[22;2~', '\x1b[37~'),
- 'shift+f12': fa('\x1b[23;2~', '\x1b[38~'),
- 'np_0': na('\x1bOp', '0'),
- 'np_1': na('\x1bOq', '1'),
- 'np_2': na('\x1bOr', '2'),
- 'np_3': na('\x1bOs', '3'),
- 'np_4': na('\x1bOt', '4'),
- 'np_5': na('\x1bOu', '5'),
- 'np_6': na('\x1bOv', '6'),
- 'np_7': na('\x1bOw', '7'),
- 'np_8': na('\x1bOx', '8'),
- 'np_9': na('\x1bOy', '9'),
- 'np_mul': na('\x1bOR', '*'),
- 'np_add': na('\x1bOl', '+'),
- 'np_sub': na('\x1bOS', '-'),
- 'np_point': na('\x1bOn', '.'),
- 'np_div': na('\x1bOQ', '/'),
- // we don't implement numlock key (should change in numpad_alt mode, but it's even more useless than the rest)
- };
-
- for (var k in keymap) {
- if (keymap.hasOwnProperty(k)) {
- bind(k, keymap[k]);
- }
- }
- }
-
- /** Bind a keystroke to message */
- function bind(combo, str) {
- // mac fix - allow also cmd
- if (combo.indexOf('ctrl+') !== -1) {
- combo += ',' + combo.replace('ctrl', 'command');
- }
-
- // unbind possible old binding
- key.unbind(combo);
-
- key(combo, function (e) {
- if (opts.no_keys) return;
- e.preventDefault();
- sendStrMsg(str)
- });
- }
-
- /** Bind/rebind key messages */
- function _initKeys() {
- // This takes care of text characters typed
- window.addEventListener('keypress', function(evt) {
- if (opts.no_keys) return;
- var str = '';
- if (evt.key) str = evt.key;
- else if (evt.which) str = String.fromCodePoint(evt.which);
- if (str.length>0 && str.charCodeAt(0) >= 32) {
-// console.log("Typed ", str);
- sendStrMsg(str);
- }
- });
-
- // ctrl-letter codes are sent as simple low ASCII codes
- for (var i = 1; i<=26;i++) {
- bind('ctrl+' + String.fromCharCode(96+i), String.fromCharCode(i));
- }
- bind('ctrl+]', '\x1b'); // alternate way to enter ESC
- bind('ctrl+\\', '\x1c');
- bind('ctrl+[', '\x1d');
- bind('ctrl+^', '\x1e');
- bind('ctrl+_', '\x1f');
-
- _bindFnKeys();
- }
-
- // mouse button states
- var mb1 = 0;
- var mb2 = 0;
- var mb3 = 0;
-
- /** Init the Input module */
- function init() {
- _initKeys();
-
- // Button presses
- qsa('#action-buttons button').forEach(function(s) {
- s.addEventListener('click', function() {
- sendBtnMsg(+this.dataset['n']);
- });
- });
-
- // global mouse state tracking - for motion reporting
- window.addEventListener('mousedown', function(evt) {
- if (evt.button == 0) mb1 = 1;
- if (evt.button == 1) mb2 = 1;
- if (evt.button == 2) mb3 = 1;
- });
-
- window.addEventListener('mouseup', function(evt) {
- if (evt.button == 0) mb1 = 0;
- if (evt.button == 1) mb2 = 0;
- if (evt.button == 2) mb3 = 0;
- });
- }
-
- /** Prepare modifiers byte for mouse message */
- function packModifiersForMouse() {
- return (key.isModifier('ctrl')?1:0) |
- (key.isModifier('shift')?2:0) |
- (key.isModifier('alt')?4:0) |
- (key.isModifier('meta')?8:0);
- }
-
- return {
- /** Init the Input module */
- init: init,
-
- /** Send a literal string message */
- sendString: sendStrMsg,
-
- /** Enable alternate key modes (cursors, numpad, fn) */
- setAlts: function(cu, np, fn) {
- if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn) {
- opts.cu_alt = cu;
- opts.np_alt = np;
- opts.fn_alt = fn;
-
- // rebind keys - codes have changed
- _bindFnKeys();
- }
- },
-
- setMouseMode: function(click, move) {
- opts.mt_click = click;
- opts.mt_move = move;
- },
-
- // Mouse events
- onMouseMove: function (x, y) {
- if (!opts.mt_move) return;
- var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0;
- var m = packModifiersForMouse();
- Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
- },
- onMouseDown: function (x, y, b) {
- if (!opts.mt_click) return;
- if (b > 3 || b < 1) return;
- var m = packModifiersForMouse();
- Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
- // console.log("B ",b," M ",m);
- },
- onMouseUp: function (x, y, b) {
- if (!opts.mt_click) return;
- if (b > 3 || b < 1) return;
- var m = packModifiersForMouse();
- Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
- // console.log("B ",b," M ",m);
- },
- onMouseWheel: function (x, y, dir) {
- if (!opts.mt_click) return;
- // -1 ... btn 4 (away from user)
- // +1 ... btn 5 (towards user)
- var m = packModifiersForMouse();
- var b = (dir < 0 ? 4 : 5);
- Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
- // console.log("B ",b," M ",m);
- },
- mouseTracksClicks: function() {
- return opts.mt_click;
- },
- blockKeys: function(yes) {
- opts.no_keys = yes;
- }
- };
-})();
-
-
-/** File upload utility */
-var TermUpl = (function() {
- var fuLines, fuPos, fuTout, fuDelay, fuNL;
-
- function fuOpen() {
- fuStatus("Ready...");
- Modal.show('#fu_modal', onClose);
- $('#fu_form').toggleClass('busy', false);
- Input.blockKeys(true);
- }
-
- function onClose() {
- console.log("Upload modal closed.");
- clearTimeout(fuTout);
- fuPos = 0;
- Input.blockKeys(false);
- }
-
- function fuStatus(msg) {
- qs('#fu_prog').textContent = msg;
- }
-
- function fuSend() {
- var v = qs('#fu_text').value;
- if (!v.length) {
- fuClose();
- return;
- }
-
- fuLines = v.split('\n');
- fuPos = 0;
- fuDelay = qs('#fu_delay').value;
- fuNL = {
- 'CR': '\r',
- 'LF': '\n',
- 'CRLF': '\r\n',
- }[qs('#fu_crlf').value];
-
- $('#fu_form').toggleClass('busy', true);
- fuStatus("Starting...");
- fuSendLine();
- }
-
- function fuSendLine() {
- if (!$('#fu_modal').hasClass('visible')) {
- // Modal is closed, cancel
- return;
- }
-
- if (!Input.sendString(fuLines[fuPos++] + fuNL)) {
- fuStatus("FAILED!");
- return;
- }
-
- var all = fuLines.length;
-
- fuStatus(fuPos+" / "+all+ " ("+(Math.round((fuPos/all)*1000)/10)+"%)");
-
- if (fuLines.length > fuPos) {
- setTimeout(fuSendLine, fuDelay);
- } else {
- fuClose();
- }
- }
-
- function fuClose() {
- Modal.hide('#fu_modal');
- }
-
- return {
- init: function() {
- qs('#fu_file').addEventListener('change', function (evt) {
- var reader = new FileReader();
- var file = evt.target.files[0];
- console.log("Selected file type: "+file.type);
- if (!file.type.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*)/)) {
- // Deny load of blobs like img - can crash browser and will get corrupted anyway
- if (!confirm("This does not look like a text file: "+file.type+"\nReally load?")) {
- qs('#fu_file').value = '';
- return;
- }
- }
- reader.onload = function(e) {
- var txt = e.target.result.replace(/[\r\n]+/,'\n');
- qs('#fu_text').value = txt;
- };
- console.log("Loading file...");
- reader.readAsText(file);
- }, false);
- },
- close: fuClose,
- start: fuSend,
- open: fuOpen,
- }
-})();
-
/** Init the terminal sub-module - called from HTML */
window.termInit = function () {
Conn.init();
diff --git a/html_orig/jssrc/term_conn.js b/html_orig/jssrc/term_conn.js
new file mode 100644
index 0000000..f158886
--- /dev/null
+++ b/html_orig/jssrc/term_conn.js
@@ -0,0 +1,122 @@
+/** Handle connections */
+var Conn = (function() {
+ var ws;
+ var heartbeatTout;
+ var pingIv;
+ var xoff = false;
+ var autoXoffTout;
+
+ function onOpen(evt) {
+ console.log("CONNECTED");
+ }
+
+ function onClose(evt) {
+ console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
+ setTimeout(function() {
+ init();
+ }, 200);
+ // this happens when the buffer gets fucked up via invalid unicode.
+ // we basically use polling instead of socket then
+ }
+
+ function onMessage(evt) {
+ try {
+ // . = heartbeat
+ switch (evt.data.charAt(0)) {
+ case 'B':
+ case 'T':
+ case 'S':
+ Screen.load(evt.data);
+ break;
+
+ case '-':
+ //console.log('xoff');
+ xoff = true;
+ autoXoffTout = setTimeout(function(){xoff=false;}, 250);
+ break;
+
+ case '+':
+ //console.log('xon');
+ xoff = false;
+ clearTimeout(autoXoffTout);
+ break;
+ }
+ heartbeat();
+ } catch(e) {
+ console.error(e);
+ }
+ }
+
+ function canSend() {
+ return !xoff;
+ }
+
+ function doSend(message) {
+ //console.log("TX: ", message);
+ if (xoff) {
+ // TODO queue
+ console.log("Can't send, flood control.");
+ return false;
+ }
+
+ if (!ws) return false; // for dry testing
+ if (ws.readyState != 1) {
+ console.error("Socket not ready");
+ return false;
+ }
+ if (typeof message != "string") {
+ message = JSON.stringify(message);
+ }
+ ws.send(message);
+ return true;
+ }
+
+ function init() {
+ heartbeat();
+
+ ws = new WebSocket("ws://"+_root+"/term/update.ws");
+ ws.onopen = onOpen;
+ ws.onclose = onClose;
+ ws.onmessage = onMessage;
+
+ console.log("Opening socket.");
+
+ // Ask for initial data
+ $.get('http://'+_root+'/term/init', function(resp, status) {
+ if (status !== 200) location.reload(true);
+ console.log("Data received!");
+ Screen.load(resp);
+ heartbeat();
+
+ showPage();
+ });
+ }
+
+ function heartbeat() {
+ clearTimeout(heartbeatTout);
+ heartbeatTout = setTimeout(heartbeatFail, 2000);
+ }
+
+ function heartbeatFail() {
+ console.error("Heartbeat lost, probing server...");
+ pingIv = setInterval(function() {
+ console.log("> ping");
+ $.get('http://'+_root+'/system/ping', function(resp, status) {
+ if (status == 200) {
+ clearInterval(pingIv);
+ console.info("Server ready, reloading page...");
+ location.reload();
+ }
+ }, {
+ timeout: 100,
+ });
+ }, 500);
+ }
+
+ return {
+ ws: null,
+ init: init,
+ send: doSend,
+ canSend: canSend, // check flood control
+ };
+})();
diff --git a/html_orig/jssrc/term_input.js b/html_orig/jssrc/term_input.js
new file mode 100644
index 0000000..67f43d1
--- /dev/null
+++ b/html_orig/jssrc/term_input.js
@@ -0,0 +1,262 @@
+/**
+ * User input
+ *
+ * --- Rx messages: ---
+ * S - screen content (binary encoding of the entire screen with simple compression)
+ * T - text labels - Title and buttons, \0x01-separated
+ * B - beep
+ * . - heartbeat
+ *
+ * --- Tx messages ---
+ * s - string
+ * b - action button
+ * p - mb press
+ * r - mb release
+ * m - mouse move
+ */
+var Input = (function() {
+ var opts = {
+ np_alt: false,
+ cu_alt: false,
+ fn_alt: false,
+ mt_click: false,
+ mt_move: false,
+ no_keys: false,
+ };
+
+ /** Send a literal message */
+ function sendStrMsg(str) {
+ return Conn.send("s"+str);
+ }
+
+ /** Send a button event */
+ function sendBtnMsg(n) {
+ Conn.send("b"+Chr(n));
+ }
+
+ /** Fn alt choice for key message */
+ function fa(alt, normal) {
+ return opts.fn_alt ? alt : normal;
+ }
+
+ /** Cursor alt choice for key message */
+ function ca(alt, normal) {
+ return opts.cu_alt ? alt : normal;
+ }
+
+ /** Numpad alt choice for key message */
+ function na(alt, normal) {
+ return opts.np_alt ? alt : normal;
+ }
+
+ function _bindFnKeys() {
+ var keymap = {
+ 'tab': '\x09',
+ 'backspace': '\x08',
+ 'enter': '\x0d',
+ 'ctrl+enter': '\x0a',
+ 'esc': '\x1b',
+ 'up': ca('\x1bOA', '\x1b[A'),
+ 'down': ca('\x1bOB', '\x1b[B'),
+ 'right': ca('\x1bOC', '\x1b[C'),
+ 'left': ca('\x1bOD', '\x1b[D'),
+ 'home': ca('\x1bOH', fa('\x1b[H', '\x1b[1~')),
+ 'insert': '\x1b[2~',
+ 'delete': '\x1b[3~',
+ 'end': ca('\x1bOF', fa('\x1b[F', '\x1b[4~')),
+ 'pageup': '\x1b[5~',
+ 'pagedown': '\x1b[6~',
+ 'f1': fa('\x1bOP', '\x1b[11~'),
+ 'f2': fa('\x1bOQ', '\x1b[12~'),
+ 'f3': fa('\x1bOR', '\x1b[13~'),
+ 'f4': fa('\x1bOS', '\x1b[14~'),
+ 'f5': '\x1b[15~', // note the disconnect
+ 'f6': '\x1b[17~',
+ 'f7': '\x1b[18~',
+ 'f8': '\x1b[19~',
+ 'f9': '\x1b[20~',
+ 'f10': '\x1b[21~', // note the disconnect
+ 'f11': '\x1b[23~',
+ 'f12': '\x1b[24~',
+ 'shift+f1': fa('\x1bO1;2P', '\x1b[25~'),
+ 'shift+f2': fa('\x1bO1;2Q', '\x1b[26~'), // note the disconnect
+ 'shift+f3': fa('\x1bO1;2R', '\x1b[28~'),
+ 'shift+f4': fa('\x1bO1;2S', '\x1b[29~'), // note the disconnect
+ 'shift+f5': fa('\x1b[15;2~', '\x1b[31~'),
+ 'shift+f6': fa('\x1b[17;2~', '\x1b[32~'),
+ 'shift+f7': fa('\x1b[18;2~', '\x1b[33~'),
+ 'shift+f8': fa('\x1b[19;2~', '\x1b[34~'),
+ 'shift+f9': fa('\x1b[20;2~', '\x1b[35~'), // 35-38 are not standard - but what is?
+ 'shift+f10': fa('\x1b[21;2~', '\x1b[36~'),
+ 'shift+f11': fa('\x1b[22;2~', '\x1b[37~'),
+ 'shift+f12': fa('\x1b[23;2~', '\x1b[38~'),
+ 'np_0': na('\x1bOp', '0'),
+ 'np_1': na('\x1bOq', '1'),
+ 'np_2': na('\x1bOr', '2'),
+ 'np_3': na('\x1bOs', '3'),
+ 'np_4': na('\x1bOt', '4'),
+ 'np_5': na('\x1bOu', '5'),
+ 'np_6': na('\x1bOv', '6'),
+ 'np_7': na('\x1bOw', '7'),
+ 'np_8': na('\x1bOx', '8'),
+ 'np_9': na('\x1bOy', '9'),
+ 'np_mul': na('\x1bOR', '*'),
+ 'np_add': na('\x1bOl', '+'),
+ 'np_sub': na('\x1bOS', '-'),
+ 'np_point': na('\x1bOn', '.'),
+ 'np_div': na('\x1bOQ', '/'),
+ // we don't implement numlock key (should change in numpad_alt mode, but it's even more useless than the rest)
+ };
+
+ for (var k in keymap) {
+ if (keymap.hasOwnProperty(k)) {
+ bind(k, keymap[k]);
+ }
+ }
+ }
+
+ /** Bind a keystroke to message */
+ function bind(combo, str) {
+ // mac fix - allow also cmd
+ if (combo.indexOf('ctrl+') !== -1) {
+ combo += ',' + combo.replace('ctrl', 'command');
+ }
+
+ // unbind possible old binding
+ key.unbind(combo);
+
+ key(combo, function (e) {
+ if (opts.no_keys) return;
+ e.preventDefault();
+ sendStrMsg(str)
+ });
+ }
+
+ /** Bind/rebind key messages */
+ function _initKeys() {
+ // This takes care of text characters typed
+ window.addEventListener('keypress', function(evt) {
+ if (opts.no_keys) return;
+ var str = '';
+ if (evt.key) str = evt.key;
+ else if (evt.which) str = String.fromCodePoint(evt.which);
+ if (str.length>0 && str.charCodeAt(0) >= 32) {
+// console.log("Typed ", str);
+ sendStrMsg(str);
+ }
+ });
+
+ // ctrl-letter codes are sent as simple low ASCII codes
+ for (var i = 1; i<=26;i++) {
+ bind('ctrl+' + String.fromCharCode(96+i), String.fromCharCode(i));
+ }
+ bind('ctrl+]', '\x1b'); // alternate way to enter ESC
+ bind('ctrl+\\', '\x1c');
+ bind('ctrl+[', '\x1d');
+ bind('ctrl+^', '\x1e');
+ bind('ctrl+_', '\x1f');
+
+ _bindFnKeys();
+ }
+
+ // mouse button states
+ var mb1 = 0;
+ var mb2 = 0;
+ var mb3 = 0;
+
+ /** Init the Input module */
+ function init() {
+ _initKeys();
+
+ // Button presses
+ qsa('#action-buttons button').forEach(function(s) {
+ s.addEventListener('click', function() {
+ sendBtnMsg(+this.dataset['n']);
+ });
+ });
+
+ // global mouse state tracking - for motion reporting
+ window.addEventListener('mousedown', function(evt) {
+ if (evt.button == 0) mb1 = 1;
+ if (evt.button == 1) mb2 = 1;
+ if (evt.button == 2) mb3 = 1;
+ });
+
+ window.addEventListener('mouseup', function(evt) {
+ if (evt.button == 0) mb1 = 0;
+ if (evt.button == 1) mb2 = 0;
+ if (evt.button == 2) mb3 = 0;
+ });
+ }
+
+ /** Prepare modifiers byte for mouse message */
+ function packModifiersForMouse() {
+ return (key.isModifier('ctrl')?1:0) |
+ (key.isModifier('shift')?2:0) |
+ (key.isModifier('alt')?4:0) |
+ (key.isModifier('meta')?8:0);
+ }
+
+ return {
+ /** Init the Input module */
+ init: init,
+
+ /** Send a literal string message */
+ sendString: sendStrMsg,
+
+ /** Enable alternate key modes (cursors, numpad, fn) */
+ setAlts: function(cu, np, fn) {
+ if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn) {
+ opts.cu_alt = cu;
+ opts.np_alt = np;
+ opts.fn_alt = fn;
+
+ // rebind keys - codes have changed
+ _bindFnKeys();
+ }
+ },
+
+ setMouseMode: function(click, move) {
+ opts.mt_click = click;
+ opts.mt_move = move;
+ },
+
+ // Mouse events
+ onMouseMove: function (x, y) {
+ if (!opts.mt_move) return;
+ var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0;
+ var m = packModifiersForMouse();
+ Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
+ },
+ onMouseDown: function (x, y, b) {
+ if (!opts.mt_click) return;
+ if (b > 3 || b < 1) return;
+ var m = packModifiersForMouse();
+ Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
+ // console.log("B ",b," M ",m);
+ },
+ onMouseUp: function (x, y, b) {
+ if (!opts.mt_click) return;
+ if (b > 3 || b < 1) return;
+ var m = packModifiersForMouse();
+ Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
+ // console.log("B ",b," M ",m);
+ },
+ onMouseWheel: function (x, y, dir) {
+ if (!opts.mt_click) return;
+ // -1 ... btn 4 (away from user)
+ // +1 ... btn 5 (towards user)
+ var m = packModifiersForMouse();
+ var b = (dir < 0 ? 4 : 5);
+ Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
+ // console.log("B ",b," M ",m);
+ },
+ mouseTracksClicks: function() {
+ return opts.mt_click;
+ },
+ blockKeys: function(yes) {
+ opts.no_keys = yes;
+ }
+ };
+})();
+
diff --git a/html_orig/jssrc/term_screen.js b/html_orig/jssrc/term_screen.js
new file mode 100644
index 0000000..23f0683
--- /dev/null
+++ b/html_orig/jssrc/term_screen.js
@@ -0,0 +1,377 @@
+var Screen = (function () {
+ var W = 0, H = 0; // 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,
+ attrs: 0,
+ suppress: false, // do not turn on in blink interval (for safe moving)
+ forceOn: false, // force on unless hanging: used to keep cursor visible during move
+ hidden: false, // do not show (DEC opt)
+ hanging: false, // cursor at column "W+1" - not visible
+ };
+
+ var screen = [];
+ var blinkIval;
+ var cursorFlashStartIval;
+
+ // Some non-bold Fraktur symbols are outside the contiguous block
+ var frakturExceptions = {
+ 'C': '\u212d',
+ 'H': '\u210c',
+ 'I': '\u2111',
+ 'R': '\u211c',
+ 'Z': '\u2128',
+ };
+
+ // for BEL
+ var audioCtx = null;
+ try {
+ audioCtx = new (window.AudioContext || window.audioContext || window.webkitAudioContext)();
+ } catch (er) {
+ console.error("No AudioContext!", er);
+ }
+
+ /** 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;
+ _draw(_curCell(), false);
+ cursor.x = x;
+ cursor.y = y;
+ // Show again
+ cursor.suppress = false;
+ _draw(_curCell());
+ }
+
+ function alpha2fraktur(t) {
+ // 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));
+ }
+ }
+ return t;
+ }
+
+ /** Update cell on display. inv = invert (for cursor) */
+ function _draw(cell, inv) {
+ if (!cell) return;
+ if (typeof inv == 'undefined') {
+ inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y;
+ }
+
+ var fg, bg, cn, t;
+
+ fg = inv ? cell.bg : cell.fg;
+ bg = inv ? cell.fg : cell.bg;
+
+ t = cell.t;
+ if (!t.length) t = ' ';
+
+ cn = 'fg' + fg + ' bg' + bg;
+ if (cell.attrs & (1<<0)) cn += ' bold';
+ if (cell.attrs & (1<<1)) cn += ' faint';
+ 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';
+ t = alpha2fraktur(t);
+ }
+ if (cell.attrs & (1<<6)) cn += ' strike';
+
+ cell.slot.textContent = t;
+ cell.elem.className = cn;
+ }
+
+ /** Show entire screen */
+ function _drawAll() {
+ for (var i = W*H-1; i>=0; i--) {
+ _draw(screen[i]);
+ }
+ }
+
+ function _rebuild(rows, cols) {
+ W = cols;
+ H = rows;
+
+ /* Build screen & show */
+ var cOuter, cInner, cell, screenDiv = qs('#screen');
+
+ // Empty the screen node
+ while (screenDiv.firstChild) screenDiv.removeChild(screenDiv.firstChild);
+
+ screen = [];
+
+ for(var i = 0; i < W*H; i++) {
+ cOuter = mk('span');
+ cInner = mk('span');
+
+ /* Mouse tracking */
+ (function() {
+ var x = i % W;
+ var y = Math.floor(i / W);
+ cOuter.addEventListener('mouseenter', function (evt) {
+ Input.onMouseMove(x, y);
+ });
+ cOuter.addEventListener('mousedown', function (evt) {
+ Input.onMouseDown(x, y, evt.button+1);
+ });
+ cOuter.addEventListener('mouseup', function (evt) {
+ Input.onMouseUp(x, y, evt.button+1);
+ });
+ cOuter.addEventListener('contextmenu', function (evt) {
+ if (Input.mouseTracksClicks()) {
+ evt.preventDefault();
+ }
+ });
+ cOuter.addEventListener('mousewheel', function (evt) {
+ Input.onMouseWheel(x, y, evt.deltaY>0?1:-1);
+ return false;
+ });
+ })();
+
+ /* End of line */
+ if ((i > 0) && (i % W == 0)) {
+ screenDiv.appendChild(mk('br'));
+ }
+ /* The cell */
+ cOuter.appendChild(cInner);
+ screenDiv.appendChild(cOuter);
+
+ cell = {
+ t: ' ',
+ fg: 7,
+ bg: 0, // the colors will be replaced immediately as we receive data (user won't see this)
+ attrs: 0,
+ elem: cOuter,
+ slot: cInner,
+ x: i % W,
+ y: Math.floor(i / W),
+ };
+ screen.push(cell);
+ _draw(cell);
+ }
+ }
+
+ /** Init the terminal */
+ function _init() {
+ /* Cursor blinking */
+ clearInterval(blinkIval);
+ blinkIval = setInterval(function () {
+ cursor.a = !cursor.a;
+ if (cursor.hidden || cursor.hanging) {
+ cursor.a = false;
+ }
+
+ if (!cursor.suppress) {
+ _draw(_curCell(), cursor.forceOn || cursor.a);
+ }
+ }, 500);
+
+ /* blink attribute animation */
+ setInterval(function () {
+ $('#screen').removeClass('blink-hide');
+ setTimeout(function () {
+ $('#screen').addClass('blink-hide');
+ }, 800); // 200 ms ON
+ }, 1000);
+
+ inited = true;
+ }
+
+ // constants for decoding the update blob
+ var SEQ_SET_COLOR_ATTR = 1;
+ var SEQ_REPEAT = 2;
+ var SEQ_SET_COLOR = 3;
+ var SEQ_SET_ATTR = 4;
+
+ /** Parse received screen update object (leading S removed already) */
+ function _load_content(str) {
+ var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell;
+
+ if (!inited) _init();
+
+ var cursorMoved;
+
+ // Set size
+ num = parse2B(str, i); i += 2; // height
+ num2 = parse2B(str, i); i += 2; // width
+ if (num != H || num2 != W) {
+ _rebuild(num, num2);
+ }
+ // console.log("Size ",num, num2);
+
+ // Cursor position
+ num = parse2B(str, i); i += 2; // row
+ num2 = parse2B(str, i); i += 2; // col
+ cursorMoved = (cursor.x != num2 || cursor.y != num);
+ cursorSet(num, num2);
+ // console.log("Cursor at ",num, num2);
+
+ // Attributes
+ num = parse2B(str, i); i += 2; // fg bg attribs
+ cursor.hidden = !(num & (1<<0)); // DEC opt "visible"
+ cursor.hanging = !!(num & (1<<1));
+ // console.log("Attributes word ",num.toString(16)+'h');
+
+ Input.setAlts(
+ !!(num & (1<<2)), // cursors alt
+ !!(num & (1<<3)), // numpad alt
+ !!(num & (1<<4)) // fn keys alt
+ );
+
+ var mt_click = !!(num & (1<<5));
+ var mt_move = !!(num & (1<<6));
+ Input.setMouseMode(
+ mt_click,
+ mt_move
+ );
+ $('#screen').toggleClass('noselect', mt_move);
+
+ var show_buttons = !!(num & (1<<7));
+ var show_config_links = !!(num & (1<<8));
+ $('.x-term-conf-btn').toggleClass('hidden', !show_config_links);
+ $('#action-buttons').toggleClass('hidden', !show_buttons);
+
+ fg = 7;
+ bg = 0;
+ 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;
+ }
+ 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;
+ // console.log("Repeat x ",num);
+ for (; num>0 && ci 0 ? e(s) : " ";
+ x.style.opacity = s.length > 0 ? 1 : 0.2;
+ });
+ }
+
+ /** Audible beep for ASCII 7 */
+ function _beep() {
+ var osc, gain;
+ if (!audioCtx) return;
+
+ // Main beep
+ osc = audioCtx.createOscillator();
+ gain = audioCtx.createGain();
+ osc.connect(gain);
+ gain.connect(audioCtx.destination);
+ gain.gain.value = 0.5;
+ osc.frequency.value = 750;
+ osc.type = 'sine';
+ osc.start();
+ osc.stop(audioCtx.currentTime+0.05);
+
+ // Surrogate beep (making it sound like 'oops')
+ osc = audioCtx.createOscillator();
+ gain = audioCtx.createGain();
+ osc.connect(gain);
+ gain.connect(audioCtx.destination);
+ gain.gain.value = 0.2;
+ osc.frequency.value = 400;
+ osc.type = 'sine';
+ osc.start(audioCtx.currentTime+0.05);
+ osc.stop(audioCtx.currentTime+0.08);
+ }
+
+ /** Load screen content from a binary sequence (new) */
+ function load(str) {
+ var content = str.substr(1);
+ switch(str.charAt(0)) {
+ case 'S':
+ _load_content(content);
+ break;
+ case 'T':
+ _load_labels(content);
+ break;
+ case 'B':
+ _beep();
+ break;
+ default:
+ console.warn("Bad data message type, ignoring.");
+ console.log(str);
+ }
+ }
+
+ return {
+ load: load, // full load (string)
+ };
+})();
diff --git a/html_orig/jssrc/term_upload.js b/html_orig/jssrc/term_upload.js
new file mode 100644
index 0000000..aed0a46
--- /dev/null
+++ b/html_orig/jssrc/term_upload.js
@@ -0,0 +1,146 @@
+/** File upload utility */
+var TermUpl = (function() {
+ var lines, // array of lines without newlines
+ line_i, // current line index
+ fuTout, // timeout handle for line sending
+ send_delay_ms, // delay between lines (ms)
+ nl_str, // newline string to use
+ curLine, // current line (when using fuOil)
+ inline_pos; // Offset in line (for long lines)
+
+ // lines longer than this are split to chunks
+ // sending a super-ling string through the socket is not a good idea
+ var MAX_LINE_LEN = 128;
+
+ function fuOpen() {
+ fuStatus("Ready...");
+ Modal.show('#fu_modal', onClose);
+ $('#fu_form').toggleClass('busy', false);
+ Input.blockKeys(true);
+ }
+
+ function onClose() {
+ console.log("Upload modal closed.");
+ clearTimeout(fuTout);
+ line_i = 0;
+ Input.blockKeys(false);
+ }
+
+ function fuStatus(msg) {
+ qs('#fu_prog').textContent = msg;
+ }
+
+ function fuSend() {
+ var v = qs('#fu_text').value;
+ if (!v.length) {
+ fuClose();
+ return;
+ }
+
+ lines = v.split('\n');
+ line_i = 0;
+ inline_pos = 0; // offset in line
+ send_delay_ms = qs('#fu_delay').value;
+
+ // sanitize - 0 causes overflows
+ if (send_delay_ms <= 0) {
+ send_delay_ms = 1;
+ qs('#fu_delay').value = 1;
+ }
+
+ nl_str = {
+ 'CR': '\r',
+ 'LF': '\n',
+ 'CRLF': '\r\n',
+ }[qs('#fu_crlf').value];
+
+ $('#fu_form').toggleClass('busy', true);
+ fuStatus("Starting...");
+ fuSendLine();
+ }
+
+ function fuSendLine() {
+ if (!$('#fu_modal').hasClass('visible')) {
+ // Modal is closed, cancel
+ return;
+ }
+
+ if (!Conn.canSend()) {
+ // postpone
+ fuTout = setTimeout(fuSendLine, 1);
+ return;
+ }
+
+ if (inline_pos == 0) {
+ curLine = lines[line_i++] + nl_str;
+ }
+
+ var chunk;
+ if ((curLine.length - inline_pos) <= MAX_LINE_LEN) {
+ chunk = curLine.substr(inline_pos, MAX_LINE_LEN);
+ inline_pos = 0;
+ } else {
+ chunk = curLine.substr(inline_pos, MAX_LINE_LEN);
+ inline_pos += MAX_LINE_LEN;
+ }
+
+ console.log("-> " + chunk);
+ if (!Input.sendString(chunk)) {
+ fuStatus("FAILED!");
+ return;
+ }
+
+ var all = lines.length;
+
+ fuStatus(line_i+" / "+all+ " ("+(Math.round((line_i/all)*1000)/10)+"%)");
+
+ if (lines.length > line_i || inline_pos > 0) {
+ fuTout = setTimeout(fuSendLine, send_delay_ms);
+ } else {
+ closeWhenReady();
+ }
+ }
+
+ function closeWhenReady() {
+ if (!Conn.canSend()) {
+ fuStatus("Waiting for Tx buffer...");
+ setTimeout(closeWhenReady, 250);
+ } else {
+ fuStatus("Done.");
+ // delay to show it
+ setTimeout(function() {
+ fuClose();
+ }, 250);
+ }
+ }
+
+ function fuClose() {
+ Modal.hide('#fu_modal');
+ }
+
+ return {
+ init: function() {
+ qs('#fu_file').addEventListener('change', function (evt) {
+ var reader = new FileReader();
+ var file = evt.target.files[0];
+ console.log("Selected file type: "+file.type);
+ if (!file.type.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*)/)) {
+ // Deny load of blobs like img - can crash browser and will get corrupted anyway
+ if (!confirm("This does not look like a text file: "+file.type+"\nReally load?")) {
+ qs('#fu_file').value = '';
+ return;
+ }
+ }
+ reader.onload = function(e) {
+ var txt = e.target.result.replace(/[\r\n]+/,'\n');
+ qs('#fu_text').value = txt;
+ };
+ console.log("Loading file...");
+ reader.readAsText(file);
+ }, false);
+ },
+ close: fuClose,
+ start: fuSend,
+ open: fuOpen,
+ }
+})();
diff --git a/html_orig/packjs.sh b/html_orig/packjs.sh
index 3c881ef..0c910b2 100755
--- a/html_orig/packjs.sh
+++ b/html_orig/packjs.sh
@@ -10,4 +10,5 @@ cat jssrc/chibi.js \
jssrc/appcommon.js \
jssrc/lang.js \
jssrc/wifi.js \
+ jssrc/term_* \
jssrc/term.js > js/app.js
diff --git a/html_orig/pages/term.php b/html_orig/pages/term.php
index bc7148b..15af52b 100644
--- a/html_orig/pages/term.php
+++ b/html_orig/pages/term.php
@@ -27,7 +27,7 @@
-
+
diff --git a/user/cgi_sockets.c b/user/cgi_sockets.c
index 47793b5..2e6d8bc 100644
--- a/user/cgi_sockets.c
+++ b/user/cgi_sockets.c
@@ -7,6 +7,7 @@
#include "uart_buffer.h"
#include "ansi_parser.h"
#include "jstring.h"
+#include "uart_driver.h"
// Heartbeat interval in ms
#define HB_TIME 1000
@@ -18,6 +19,8 @@
volatile bool notify_available = true;
volatile bool notify_cooldown = false;
+volatile bool browser_wants_xon = false;
+
static ETSTimer notifyContentTim;
static ETSTimer notifyLabelsTim;
static ETSTimer notifyCooldownTim;
@@ -224,11 +227,20 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
case 's':
// pass string verbatim
if (termconf_scratch.loopback) {
- for (int i = 1; i < strlen(data); i++) {
+ for (int i = 1; i < len; i++) {
ansi_parser(data[i]);
}
}
UART_SendAsync(data+1, -1);
+
+ // TODO base this on the actual buffer empty space, not rx chunk size
+ if ((UART_AsyncTxGetEmptySpace() < 256) && !browser_wants_xon) {
+ UART_WriteChar(UART1, '-', 100);
+ cgiWebsockBroadcast(URL_WS_UPDATE, "-", 1, 0);
+ browser_wants_xon = true;
+
+ system_soft_wdt_feed();
+ }
break;
case 'b':
@@ -276,3 +288,20 @@ void ICACHE_FLASH_ATTR updateSockConnect(Websock *ws)
TIMER_START(&heartbeatTim, heartbeatTimCb, HB_TIME, 1);
}
+
+ETSTimer xonTim;
+
+void ICACHE_FLASH_ATTR notify_empty_txbuf_cb(void *unused)
+{
+ UART_WriteChar(UART1, '+', 100);
+ cgiWebsockBroadcast(URL_WS_UPDATE, "+", 1, 0);
+ browser_wants_xon = false;
+}
+
+
+void notify_empty_txbuf(void)
+{
+ if (browser_wants_xon) {
+ TIMER_START(&xonTim, notify_empty_txbuf_cb, 1, 0);
+ }
+}
diff --git a/user/cgi_sockets.h b/user/cgi_sockets.h
index 59d8282..83998cf 100644
--- a/user/cgi_sockets.h
+++ b/user/cgi_sockets.h
@@ -8,6 +8,8 @@
/** Update websocket connect callback */
void updateSockConnect(Websock *ws);
+void notify_empty_txbuf(void);
+
void send_beep(void);
// defined in the makefile
diff --git a/user/uart_buffer.c b/user/uart_buffer.c
index 6a26d11..4cfda64 100644
--- a/user/uart_buffer.c
+++ b/user/uart_buffer.c
@@ -8,8 +8,8 @@
#include
#include
-#define UART_TX_BUFFER_SIZE 256 //Ring buffer length of tx buffer
-#define UART_RX_BUFFER_SIZE 512 //Ring buffer length of rx buffer
+#define UART_TX_BUFFER_SIZE 1024 //Ring buffer length of tx buffer
+#define UART_RX_BUFFER_SIZE 1024 //Ring buffer length of rx buffer
struct UartBuffer {
uint32 UartBuffSize;
@@ -177,6 +177,11 @@ void UART_RxFifoCollect(void)
}
}
+u16 ICACHE_FLASH_ATTR UART_AsyncTxGetEmptySpace(void)
+{
+ return pTxBuffer->Space;
+}
+
/**
* Schedule data to be sent
* @param pdata
@@ -227,7 +232,7 @@ static void UART_TxFifoEnq(struct UartBuffer *pTxBuff, uint8 data_len, uint8 uar
pTxBuff->Space += data_len;
}
-
+volatile bool next_empty_it_only_for_notify = false;
/******************************************************************************
* FunctionName : TxFromBuffer
* Description : get data from the tx buffer and fill the uart tx fifo, co-work with the uart fifo empty interrupt
@@ -242,16 +247,28 @@ void UART_DispatchFromTxBuffer(uint8 uart_no)
uint16 data_len;
// if (pTxBuffer) {
- data_len = (uint8) (pTxBuffer->UartBuffSize - pTxBuffer->Space);
- if (data_len > fifo_remain) {
- len_tmp = fifo_remain;
- UART_TxFifoEnq(pTxBuffer, len_tmp, uart_no);
+ data_len = (uint8) (pTxBuffer->UartBuffSize - pTxBuffer->Space);
+ if (data_len > fifo_remain) {
+ len_tmp = fifo_remain;
+ UART_TxFifoEnq(pTxBuffer, len_tmp, uart_no);
+ SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
+ }
+ else {
+ len_tmp = (uint8) data_len;
+ UART_TxFifoEnq(pTxBuffer, len_tmp, uart_no);
+
+ // we get one more IT after fifo ends even if we have 0 more bytes
+ // for notify
+ if (next_empty_it_only_for_notify) {
+ notify_empty_txbuf();
+ next_empty_it_only_for_notify = 0;
+ } else {
+ // Done sending
+ next_empty_it_only_for_notify = 1;
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
}
- else {
- len_tmp = (uint8) data_len;
- UART_TxFifoEnq(pTxBuffer, len_tmp, uart_no);
- }
+ }
+
// }
// else {
// error("pTxBuff null \n\r");
diff --git a/user/uart_buffer.h b/user/uart_buffer.h
index f11cfd4..6b9b362 100644
--- a/user/uart_buffer.h
+++ b/user/uart_buffer.h
@@ -23,4 +23,8 @@ void UART_DispatchFromTxBuffer(uint8 uart_no);
u16 UART_AsyncRxCount(void);
+u16 UART_AsyncTxGetEmptySpace(void);
+
+extern void __attribute__((weak)) notify_empty_txbuf(void);
+
#endif //ESP_VT100_FIRMWARE_UART_BUFFER_H