From e2cdb4091ce0df5cf0817ec3fdc34f4c73502220 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 11:30:39 +0200 Subject: [PATCH 01/73] Remove refs to single-instance objects in parser --- js/term/index.js | 24 ++++++++++++++++++++++++ js/term/screen.js | 16 +++++++++++++--- js/term/screen_parser.js | 24 ++++++------------------ 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/js/term/index.js b/js/term/index.js index a382d1a..c6d8d82 100644 --- a/js/term/index.js +++ b/js/term/index.js @@ -26,6 +26,30 @@ module.exports = function (opts) { buttons.update() }) + screen.on('TEMP:hide-load-failed-msg', () => { + let scr = qs('#screen') + let errmsg = qs('#load-failed') + if (scr) scr.classList.remove('failed') + if (errmsg) errmsg.parentNode.removeChild(errmsg) + }) + + screen.on('TEMP:show-config-links', show => { + let buttons = [...document.querySelectorAll('.x-term-conf-btn')] + if (show) buttons.forEach(x => x.classList.remove('hidden')) + else buttons.forEach(x => x.classList.add('hidden')) + }) + + screen.on('TEMP:show-buttons', show => { + if (show) qs('#action-buttons').classList.remove('hidden') + else qs('#action-buttons').classList.add('hidden') + }) + + screen.on('TEMP:update-title', text => { + qs('#screen-title').textContent = text + if (!text) text = 'Terminal' + qs('title').textContent = `${text} :: ESPTerm` + }) + let showSplashTimeout = null let showSplash = (obj, delay = 250) => { clearTimeout(showSplashTimeout) diff --git a/js/term/screen.js b/js/term/screen.js index 2fb13eb..9c9fe1f 100644 --- a/js/term/screen.js +++ b/js/term/screen.js @@ -60,7 +60,8 @@ module.exports = class TermScreen extends EventEmitter { gridScaleY: 1.2, fitIntoWidth: 0, fitIntoHeight: 0, - debug: false, + // two bits. LSB: debug enabled by user, MSB: debug enabled by server + debug: 0, graphics: 0, statusScreen: null } @@ -85,6 +86,8 @@ module.exports = class TermScreen extends EventEmitter { fitIntoHeight: 0 } + const self = this + // current selection this.selection = { // when false, this will prevent selection in favor of mouse events, @@ -93,14 +96,21 @@ module.exports = class TermScreen extends EventEmitter { // selection start and end (x, y) tuples start: [0, 0], - end: [0, 0] + end: [0, 0], + + setSelectable (value) { + if (value !== this.selectable) { + this.selectable = value + if (value) self.classList.add('selectable') + else self.classList.remove('selectable') + } + } } // mouse features this.mouseMode = { clicks: false, movement: false } // make writing to window update size and draw - const self = this this.window = new Proxy(this._window, { set (target, key, value, receiver) { if (target[key] !== value) { diff --git a/js/term/screen_parser.js b/js/term/screen_parser.js index 93f4b09..6c6149e 100644 --- a/js/term/screen_parser.js +++ b/js/term/screen_parser.js @@ -1,6 +1,3 @@ -const $ = require('../lib/chibi') -const { qs } = require('../utils') - const { ATTR_FG, ATTR_BG, @@ -66,10 +63,7 @@ module.exports = class ScreenParser { */ hideLoadFailedMsg () { if (!this.contentLoaded) { - let scr = qs('#screen') - let errmsg = qs('#load-failed') - if (scr) scr.classList.remove('failed') - if (errmsg) errmsg.parentNode.removeChild(errmsg) + this.screen.emit('TEMP:hide-load-failed-msg') this.contentLoaded = true } } @@ -150,8 +144,7 @@ module.exports = class ScreenParser { } this.screen.input.setMouseMode(trackMouseClicks, trackMouseMovement) - this.screen.selection.selectable = !trackMouseClicks && !trackMouseMovement - $(this.screen.canvas).toggleClass('selectable', this.screen.selection.selectable) + this.screen.selection.setSelectable(!trackMouseClicks && !trackMouseMovement) this.screen.mouseMode = { clicks: trackMouseClicks, movement: trackMouseMovement @@ -160,15 +153,15 @@ module.exports = class ScreenParser { const showButtons = !!(attributes & OPT_SHOW_BUTTONS) const showConfigLinks = !!(attributes & OPT_SHOW_CONFIG_LINKS) - $('.x-term-conf-btn').toggleClass('hidden', !showConfigLinks) - $('#action-buttons').toggleClass('hidden', !showButtons) + this.screen.emit('TEMP:show-config-links', showConfigLinks) + this.screen.emit('TEMP:show-buttons', showButtons) this.screen.bracketedPaste = !!(attributes & OPT_BRACKETED_PASTE) this.screen.reverseVideo = !!(attributes & OPT_REVERSE_VIDEO) const debugbar = !!(attributes & OPT_DEBUGBAR) - // TODO do something with debugbar + this.screen.window.debug &= 0b01 | (+debugbar < 1) } else if (topic === TOPIC_CURSOR) { // cursor position @@ -193,12 +186,7 @@ module.exports = class ScreenParser { this.screen.renderer.scheduleDraw('cursor-moved') } else if (topic === TOPIC_TITLE) { - - text = collectOneTerminatedString() - qs('#screen-title').textContent = text - if (text.length === 0) text = 'Terminal' - qs('title').textContent = `${text} :: ESPTerm` - + this.screen.emit('TEMP:update-title', collectOneTerminatedString()) } else if (topic === TOPIC_BUTTONS) { const count = du(strArray[ci++]) From 3cf83d0fc5f4e2b6918719aeaef52a84b6b10b72 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 11:36:26 +0200 Subject: [PATCH 02/73] Fix derp in e2cdb40 --- js/term/screen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/term/screen.js b/js/term/screen.js index 9c9fe1f..3e351de 100644 --- a/js/term/screen.js +++ b/js/term/screen.js @@ -101,8 +101,8 @@ module.exports = class TermScreen extends EventEmitter { setSelectable (value) { if (value !== this.selectable) { this.selectable = value - if (value) self.classList.add('selectable') - else self.classList.remove('selectable') + if (value) self.canvas.classList.add('selectable') + else self.canvas.classList.remove('selectable') } } } From 394654099a066427fdde54b48b7bab7179ca8198 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 11:55:47 +0200 Subject: [PATCH 03/73] Move color mapping to themes --- js/term/screen_renderer.js | 24 ++---------------------- js/term/themes.js | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/js/term/screen_renderer.js b/js/term/screen_renderer.js index 0a174bd..1f935d2 100644 --- a/js/term/screen_renderer.js +++ b/js/term/screen_renderer.js @@ -1,7 +1,7 @@ const { themes, buildColorTable, - SELECTION_FG, SELECTION_BG + getColor } = require('./themes') const { @@ -118,27 +118,7 @@ module.exports = class ScreenRenderer { * @returns {string} the CSS color */ getColor (i) { - // return palette color if it exists - if (i < 16 && i in this.palette) return this.palette[i] - - // -1 for selection foreground, -2 for selection background - if (i === -1) return SELECTION_FG - if (i === -2) return SELECTION_BG - - // 256 color - if (i > 15 && i < 256) return this.colorTable256[i] - - // true color, encoded as (hex) + 256 (such that #000 == 256) - if (i > 255) { - i -= 256 - let red = (i >> 16) & 0xFF - let green = (i >> 8) & 0xFF - let blue = i & 0xFF - return `rgb(${red}, ${green}, ${blue})` - } - - // return error color - return (Date.now() / 1000) % 2 === 0 ? '#f0f' : '#0f0' + return getColor(i, this.palette) } /** diff --git a/js/term/themes.js b/js/term/themes.js index 8fb47ac..5c65533 100644 --- a/js/term/themes.js +++ b/js/term/themes.js @@ -73,7 +73,7 @@ exports.buildColorTable = function () { if (colorTable256 !== null) return colorTable256 // 256color lookup table - // should not be used to look up 0-15 (will return transparent) + // should not be used to look up 0-15 colorTable256 = new Array(16).fill('#000000') // fill color table @@ -113,6 +113,35 @@ exports.themePreview = function (themeN) { }) } +exports.colorTable256 = null +exports.ensureColorTable256 = function () { + if (!exports.colorTable256) exports.colorTable256 = exports.buildColorTable() +} + +exports.getColor = function (i, palette = []) { + // return palette color if it exists + if (i < 16 && i in palette) return palette[i] + + // -1 for selection foreground, -2 for selection background + if (i === -1) return exports.SELECTION_FG + if (i === -2) return exports.SELECTION_BG + + // 256 color + if (i > 15 && i < 256) { + exports.ensureColorTable256() + return exports.colorTable256[i] + } + + // 24-bit color, encoded as (hex) + 256 (such that #000000 == 256) + if (i > 255) { + i -= 256 + return '#' + `000000${i.toString(16)}`.substr(-6) + } + + // return error color + return Math.floor(Date.now() / 1000) % 2 === 0 ? '#f0f' : '#0f0' +} + exports.toHex = function (shade, themeN) { if (/^\d+$/.test(shade)) { shade = +shade From 74ce1de432cf8a205d1efc15838775d69155d14d Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 12:03:25 +0200 Subject: [PATCH 04/73] Make themes.toHex use getColor, ensure .selectable --- js/term/screen.js | 2 +- js/term/themes.js | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/js/term/screen.js b/js/term/screen.js index 3e351de..f8d7b89 100644 --- a/js/term/screen.js +++ b/js/term/screen.js @@ -92,7 +92,7 @@ module.exports = class TermScreen extends EventEmitter { this.selection = { // when false, this will prevent selection in favor of mouse events, // though alt can be held to override it - selectable: true, + selectable: null, // selection start and end (x, y) tuples start: [0, 0], diff --git a/js/term/themes.js b/js/term/themes.js index 5c65533..ea0feb8 100644 --- a/js/term/themes.js +++ b/js/term/themes.js @@ -145,10 +145,7 @@ exports.getColor = function (i, palette = []) { exports.toHex = function (shade, themeN) { if (/^\d+$/.test(shade)) { shade = +shade - if (shade < 16) shade = themes[themeN][shade] - else { - shade = exports.buildColorTable()[shade] - } + return exports.getColor(shade, themes[themeN]) } return shade } From d154df6360f4fdefcd1586957915adc6c1b26b2c Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 12:04:24 +0200 Subject: [PATCH 05/73] Stop logging heartbeats --- js/term/connection.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/term/connection.js b/js/term/connection.js index d53966c..4e48d34 100644 --- a/js/term/connection.js +++ b/js/term/connection.js @@ -82,7 +82,6 @@ module.exports = class TermConnection extends EventEmitter { onDecodedWSMessage (str) { switch (str.charAt(0)) { case '.': - console.log(str) // heartbeat, no-op message break From 735ee5834032dda051ae96b88a7572348e4b8e75 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 13:11:48 +0200 Subject: [PATCH 06/73] Start removing references to screen in parser --- js/term/screen.js | 77 +++++++- js/term/screen_parser.js | 415 ++++++++++++++++++++------------------- 2 files changed, 285 insertions(+), 207 deletions(-) diff --git a/js/term/screen.js b/js/term/screen.js index f8d7b89..17480f1 100644 --- a/js/term/screen.js +++ b/js/term/screen.js @@ -472,6 +472,15 @@ module.exports = class TermScreen extends EventEmitter { } } + resetScreen () { + const { width, height } = this.window + this.blinkingCellCount = 0 + this.screen.screen = new Array(width * height).fill(' ') + this.screen.screenFG = new Array(width * height).fill(0) + this.screen.screenBG = new Array(width * height).fill(0) + this.screen.screenAttrs = new Array(width * height).fill(0) + } + /** * Returns a normalized version of the current selection, such that `start` * is always before `end`. @@ -628,6 +637,72 @@ module.exports = class TermScreen extends EventEmitter { } load (...args) { - this.parser.load(...args) + const updates = this.parser.parse(...args) + + for (let update of updates) { + switch (update.topic) { + case 'screen-opts': + if (update.width !== this.window.width || update.height !== this.window.height) { + this.window.width = update.width + this.window.height = update.height + this.resetScreen() + } + this.renderer.loadTheme(update.theme) + this.renderer.setDefaultColors(update.defFG, update.defBG) + this.cursor.visible = update.cursorVisible + this.input.setAlts(...update.inputAlts) + this.mouseMode.clicks = update.trackMouseClicks + this.mouseMode.movement = update.trackMouseMovement + this.input.setMouseMode(update.trackMouseClicks, update.trackMouseMovement) + this.selection.setSelectable(!update.trackMouseClicks && !update.trackMouseMovement) + if (this.cursor.blinking !== update.cursorBlinking) { + this.cursor.blinking = update.cursorBlinking + this.renderer.resetCursorBlink() + } + this.cursor.style = update.cursorStyle + this.bracketedPaste = update.bracketedPaste + this.reverseVideo = update.reverseVideo + this.window.debug &= 0b01 + this.window.debug |= (+update.debugEnabled << 1) + + this.emit('TEMP:show-buttons', update.showButtons) + this.emit('TEMP:show-links', update.showConfigLinks) + break + + case 'cursor': + if (this.cursor.x !== update.x || this.cursor.y !== update.y) { + this.cursor.x = update.x + this.cursor.y = update.y + this.cursor.hanging = update.hanging + this.renderer.resetCursorBlink() + this.emit('cursor-moved') + this.renderer.scheduleDraw('cursor-moved') + } + break + + case 'title': + this.emit('TEMP:update-title', update.title) + break + + case 'button-labels': + this.emit('button-labels', update.labels) + break + + case 'bell': + this.beep() + break + + case 'internal': + this.emit('internal', update) + break + + case 'content': + update.tempDoNotCommitUpstream() + break + + default: + console.log('Unhandled update', update) + } + } } } diff --git a/js/term/screen_parser.js b/js/term/screen_parser.js index 6c6149e..46bbac9 100644 --- a/js/term/screen_parser.js +++ b/js/term/screen_parser.js @@ -19,6 +19,7 @@ const SEQ_SET_BG = 6 const SEQ_SET_ATTR_0 = 7 function du (str) { + if (!str) return NaN let num = str.codePointAt(0) if (num > 0xDFFF) num -= 0x800 return num - 1 @@ -52,7 +53,15 @@ const OPT_REVERSE_VIDEO = (1 << 14) module.exports = class ScreenParser { constructor (screen) { - this.screen = screen + // this.screen = screen + let didWarn = false + Object.defineProperty(this, 'screen', { + get () { + if (!didWarn) console.warn('Deprecated get ScreenParser#screen') + didWarn = true + return screen + } + }) // true if TermScreen#load was called at least once this.contentLoaded = false @@ -68,14 +77,13 @@ module.exports = class ScreenParser { } } - loadUpdate (str) { + parseUpdate (str) { // console.log(`update ${str}`) // current index let ci = 0 let strArray = Array.from ? Array.from(str) : str.split('') let text - let resized = false const topics = du(strArray[ci++]) // this.screen.cursor.hanging = !!(attributes & (1 << 1)) @@ -93,35 +101,30 @@ module.exports = class ScreenParser { return text } + const updates = [] + while (ci < strArray.length) { const topic = strArray[ci++] if (topic === TOPIC_SCREEN_OPTS) { - const newHeight = du(strArray[ci++]) - const newWidth = du(strArray[ci++]) + const height = du(strArray[ci++]) + const width = du(strArray[ci++]) const theme = du(strArray[ci++]) - const defFg = (du(strArray[ci++]) & 0xFFFF) | ((du(strArray[ci++]) & 0xFFFF) << 16) - const defBg = (du(strArray[ci++]) & 0xFFFF) | ((du(strArray[ci++]) & 0xFFFF) << 16) - const attributes = du(strArray[ci++]) - - // theming - this.screen.renderer.loadTheme(theme) - this.screen.renderer.setDefaultColors(defFg, defBg) - - // apply size - resized = (this.screen.window.height !== newHeight) || (this.screen.window.width !== newWidth) - this.screen.window.height = newHeight - this.screen.window.width = newWidth + const defFG = (du(strArray[ci++]) & 0xFFFF) | ((du(strArray[ci++]) & 0xFFFF) << 16) + const defBG = (du(strArray[ci++]) & 0xFFFF) | ((du(strArray[ci++]) & 0xFFFF) << 16) // process attributes - this.screen.cursor.visible = !!(attributes & OPT_CURSOR_VISIBLE) + const attributes = du(strArray[ci++]) - this.screen.input.setAlts( + const cursorVisible = !!(attributes & OPT_CURSOR_VISIBLE) + + // HACK: input alts are formatted as arguments for Input#setAlts + const inputAlts = [ !!(attributes & OPT_CURSORS_ALT_MODE), !!(attributes & OPT_NUMPAD_ALT_MODE), !!(attributes & OPT_FN_ALT_MODE), !!(attributes & OPT_CRLF_MODE) - ) + ] const trackMouseClicks = !!(attributes & OPT_CLICK_TRACKING) const trackMouseMovement = !!(attributes & OPT_MOVE_TRACKING) @@ -133,60 +136,58 @@ module.exports = class ScreenParser { // if it's not zero, decrement such that the two most significant bits // are the type and the least significant bit is the blink state if (cursorShape > 0) cursorShape-- - const cursorStyle = cursorShape >> 1 + let cursorStyle = cursorShape >> 1 const cursorBlinking = !(cursorShape & 1) - if (cursorStyle === 0) this.screen.cursor.style = 'block' - else if (cursorStyle === 1) this.screen.cursor.style = 'line' - else if (cursorStyle === 2) this.screen.cursor.style = 'bar' + if (cursorStyle === 0) cursorStyle = 'block' + else if (cursorStyle === 1) cursorStyle = 'line' + else cursorStyle = 'bar' + if (this.screen.cursor.blinking !== cursorBlinking) { this.screen.cursor.blinking = cursorBlinking this.screen.renderer.resetCursorBlink() } - this.screen.input.setMouseMode(trackMouseClicks, trackMouseMovement) - this.screen.selection.setSelectable(!trackMouseClicks && !trackMouseMovement) - this.screen.mouseMode = { - clicks: trackMouseClicks, - movement: trackMouseMovement - } - const showButtons = !!(attributes & OPT_SHOW_BUTTONS) const showConfigLinks = !!(attributes & OPT_SHOW_CONFIG_LINKS) - this.screen.emit('TEMP:show-config-links', showConfigLinks) - this.screen.emit('TEMP:show-buttons', showButtons) - - this.screen.bracketedPaste = !!(attributes & OPT_BRACKETED_PASTE) - this.screen.reverseVideo = !!(attributes & OPT_REVERSE_VIDEO) - - const debugbar = !!(attributes & OPT_DEBUGBAR) - - this.screen.window.debug &= 0b01 | (+debugbar < 1) + const bracketedPaste = !!(attributes & OPT_BRACKETED_PASTE) + const reverseVideo = !!(attributes & OPT_REVERSE_VIDEO) + + const debugEnabled = !!(attributes & OPT_DEBUGBAR) + + updates.push({ + topic: 'screen-opts', + width, + height, + theme, + defFG, + defBG, + cursorVisible, + cursorBlinking, + cursorStyle, + inputAlts, + trackMouseClicks, + trackMouseMovement, + showButtons, + showConfigLinks, + bracketedPaste, + reverseVideo, + debugEnabled + }) } else if (topic === TOPIC_CURSOR) { - // cursor position - const cursorY = du(strArray[ci++]) - const cursorX = du(strArray[ci++]) - const hanging = du(strArray[ci++]) - - const cursorMoved = ( - hanging !== this.screen.cursor.hanging || - cursorX !== this.screen.cursor.x || - cursorY !== this.screen.cursor.y) - - this.screen.cursor.x = cursorX - this.screen.cursor.y = cursorY - - this.screen.cursor.hanging = !!hanging - - if (cursorMoved) { - this.screen.renderer.resetCursorBlink() - this.screen.emit('cursor-moved') - } - - this.screen.renderer.scheduleDraw('cursor-moved') + const y = du(strArray[ci++]) + const x = du(strArray[ci++]) + const hanging = !!du(strArray[ci++]) + + updates.push({ + topic: 'cursor', + x, + y, + hanging + }) } else if (topic === TOPIC_TITLE) { - this.screen.emit('TEMP:update-title', collectOneTerminatedString()) + updates.push({ topic: 'title', title: collectOneTerminatedString() }) } else if (topic === TOPIC_BUTTONS) { const count = du(strArray[ci++]) @@ -196,16 +197,14 @@ module.exports = class ScreenParser { labels.push(text) } - this.screen.emit('button-labels', labels) + updates.push({ + topic: 'button-labels', + labels + }) } else if (topic === TOPIC_BACKDROP) { - - text = collectOneTerminatedString() - this.screen.backgroundImage = text - + updates.push({ topic: 'backdrop', image: collectOneTerminatedString() }) } else if (topic === TOPIC_BELL) { - - this.screen.beep() - + updates.push({ topic: 'bell' }) } else if (topic === TOPIC_INTERNAL) { // debug info @@ -219,7 +218,8 @@ module.exports = class ScreenParser { const freeHeap = du(strArray[ci++]) const clientCount = du(strArray[ci++]) - this.screen.emit('internal', { + updates.push({ + topic: 'internal', flags, cursorAttrs, regionStart, @@ -232,163 +232,164 @@ module.exports = class ScreenParser { }) } else if (topic === TOPIC_CONTENT) { // set screen content + let _ci = ci - const frameY = du(strArray[ci++]) - const frameX = du(strArray[ci++]) - const frameHeight = du(strArray[ci++]) - const frameWidth = du(strArray[ci++]) - - if (this.screen._debug && this.screen.window.debug) { - this.screen._debug.pushFrame([frameX, frameY, frameWidth, frameHeight]) - } - - // content - let fg = 7 - let bg = 0 - let attrs = 0 - let cell = 0 // cell index - let lastChar = ' ' - let frameLength = frameWidth * frameHeight - let screenLength = this.screen.window.width * this.screen.window.height - - if (resized) { - this.screen.updateSize() - this.screen.blinkingCellCount = 0 - this.screen.screen = new Array(screenLength).fill(' ') - this.screen.screenFG = new Array(screenLength).fill(' ') - this.screen.screenBG = new Array(screenLength).fill(' ') - this.screen.screenAttrs = new Array(screenLength).fill(0) - } + let tempDoNotCommitUpstream = () => { + let ci = _ci + const frameY = du(strArray[ci++]) + const frameX = du(strArray[ci++]) + const frameHeight = du(strArray[ci++]) + const frameWidth = du(strArray[ci++]) - const MASK_LINE_ATTR = ATTR_UNDERLINE | ATTR_OVERLINE | ATTR_STRIKE - const MASK_BLINK = ATTR_BLINK - - let pushCell = () => { - // Remove blink attribute if it wouldn't have any effect - let myAttrs = attrs - let hasFG = attrs & ATTR_FG - let hasBG = attrs & ATTR_BG - let cellFG = fg - let cellBG = bg - - // use 0,0 if no fg/bg. this is to match back-end implementation - // and allow leaving out fg/bg setting for cells with none - if (!hasFG) cellFG = 0 - if (!hasBG) cellBG = 0 - - if ((myAttrs & MASK_BLINK) !== 0 && - ((lastChar === ' ' && ((myAttrs & MASK_LINE_ATTR) === 0)) || // no line styles - (fg === bg && hasFG && hasBG) // invisible text - ) - ) { - myAttrs ^= MASK_BLINK + if (this.screen._debug && this.screen.window.debug) { + this.screen._debug.pushFrame([frameX, frameY, frameWidth, frameHeight]) } - // update blinking cells counter if blink state changed - if ((this.screen.screenAttrs[cell] & MASK_BLINK) !== (myAttrs & MASK_BLINK)) { - if (myAttrs & MASK_BLINK) this.screen.blinkingCellCount++ - else this.screen.blinkingCellCount-- - } - - let cellXInFrame = cell % frameWidth - let cellYInFrame = Math.floor(cell / frameWidth) - let index = (frameY + cellYInFrame) * this.screen.window.width + frameX + cellXInFrame - // 8 dark system colors turn bright when bold - if ((myAttrs & ATTR_BOLD) && !(myAttrs & ATTR_FAINT) && hasFG && cellFG < 8) { - cellFG += 8 + // content + let fg = 7 + let bg = 0 + let attrs = 0 + let cell = 0 // cell index + let lastChar = ' ' + let frameLength = frameWidth * frameHeight + + const MASK_LINE_ATTR = ATTR_UNDERLINE | ATTR_OVERLINE | ATTR_STRIKE + const MASK_BLINK = ATTR_BLINK + + let pushCell = () => { + // Remove blink attribute if it wouldn't have any effect + let myAttrs = attrs + let hasFG = attrs & ATTR_FG + let hasBG = attrs & ATTR_BG + let cellFG = fg + let cellBG = bg + + // use 0,0 if no fg/bg. this is to match back-end implementation + // and allow leaving out fg/bg setting for cells with none + if (!hasFG) cellFG = 0 + if (!hasBG) cellBG = 0 + + if ((myAttrs & MASK_BLINK) !== 0 && + ((lastChar === ' ' && ((myAttrs & MASK_LINE_ATTR) === 0)) || // no line styles + (fg === bg && hasFG && hasBG) // invisible text + ) + ) { + myAttrs ^= MASK_BLINK + } + // update blinking cells counter if blink state changed + if ((this.screen.screenAttrs[cell] & MASK_BLINK) !== (myAttrs & MASK_BLINK)) { + if (myAttrs & MASK_BLINK) this.screen.blinkingCellCount++ + else this.screen.blinkingCellCount-- + } + + let cellXInFrame = cell % frameWidth + let cellYInFrame = Math.floor(cell / frameWidth) + let index = (frameY + cellYInFrame) * this.screen.window.width + frameX + cellXInFrame + + // 8 dark system colors turn bright when bold + if ((myAttrs & ATTR_BOLD) && !(myAttrs & ATTR_FAINT) && hasFG && cellFG < 8) { + cellFG += 8 + } + + this.screen.screen[index] = lastChar + this.screen.screenFG[index] = cellFG + this.screen.screenBG[index] = cellBG + this.screen.screenAttrs[index] = myAttrs } - this.screen.screen[index] = lastChar - this.screen.screenFG[index] = cellFG - this.screen.screenBG[index] = cellBG - this.screen.screenAttrs[index] = myAttrs - } - - while (ci < strArray.length && cell < frameLength) { - let character = strArray[ci++] - let charCode = character.codePointAt(0) - - let data, count - switch (charCode) { - case SEQ_REPEAT: - count = du(strArray[ci++]) - for (let j = 0; j < count; j++) { + while (ci < strArray.length && cell < frameLength) { + let character = strArray[ci++] + let charCode = character.codePointAt(0) + + let data, count + switch (charCode) { + case SEQ_REPEAT: + count = du(strArray[ci++]) + for (let j = 0; j < count; j++) { + pushCell() + if (++cell > frameLength) break + } + break + + case SEQ_SKIP: + cell += du(strArray[ci++]) + break + + case SEQ_SET_COLORS: + data = du(strArray[ci++]) + fg = data & 0xFF + bg = (data >> 8) & 0xFF + break + + case SEQ_SET_ATTRS: + data = du(strArray[ci++]) + attrs = data & 0xFFFF + break + + case SEQ_SET_ATTR_0: + attrs = 0 + break + + case SEQ_SET_FG: + data = du(strArray[ci++]) + if (data & 0x10000) { + data &= 0xFFF + data |= (du(strArray[ci++]) & 0xFFF) << 12 + data += 256 + } + fg = data + break + + case SEQ_SET_BG: + data = du(strArray[ci++]) + if (data & 0x10000) { + data &= 0xFFF + data |= (du(strArray[ci++]) & 0xFFF) << 12 + data += 256 + } + bg = data + break + + default: + if (charCode < 32) character = '\ufffd' + lastChar = character pushCell() - if (++cell > frameLength) break - } - break - - case SEQ_SKIP: - cell += du(strArray[ci++]) - break - - case SEQ_SET_COLORS: - data = du(strArray[ci++]) - fg = data & 0xFF - bg = (data >> 8) & 0xFF - break - - case SEQ_SET_ATTRS: - data = du(strArray[ci++]) - attrs = data & 0xFFFF - break - - case SEQ_SET_ATTR_0: - attrs = 0 - break - - case SEQ_SET_FG: - data = du(strArray[ci++]) - if (data & 0x10000) { - data &= 0xFFF - data |= (du(strArray[ci++]) & 0xFFF) << 12 - data += 256 - } - fg = data - break - - case SEQ_SET_BG: - data = du(strArray[ci++]) - if (data & 0x10000) { - data &= 0xFFF - data |= (du(strArray[ci++]) & 0xFFF) << 12 - data += 256 - } - bg = data - break - - default: - if (charCode < 32) character = '\ufffd' - lastChar = character - pushCell() - cell++ + cell++ + } } - } - if (this.screen.window.debug) console.log(`Blinky cells: ${this.screen.blinkingCellCount}`) + if (this.screen.window.debug) console.log(`Blinky cells: ${this.screen.blinkingCellCount}`) - this.screen.renderer.scheduleDraw('load', 16) - this.screen.conn.emit('load') + this.screen.renderer.scheduleDraw('load', 16) + this.screen.conn.emit('load') + } + updates.push({ + topic: 'content', + tempDoNotCommitUpstream + }) } if ((topics & 0x3B) !== 0) this.hideLoadFailedMsg() } + + return updates } /** - * Loads a message from the server, and optionally a theme. - * @param {string} str - the message + * Parses a message from the server + * @param {string} message - the message */ - load (str) { - const content = str.substr(1) + parse (message) { + const content = message.substr(1) + const updates = [] // This is a good place for debugging the message - // console.log(str) + // console.log(message) - switch (str[0]) { + switch (message[0]) { case 'U': - this.loadUpdate(content) + updates.push(...this.parseUpdate(content)) break case 'G': @@ -396,7 +397,9 @@ module.exports = class ScreenParser { break default: - console.warn(`Bad data message type; ignoring.\n${JSON.stringify(str)}`) + console.warn(`Bad data message type; ignoring.\n${JSON.stringify(message)}`) } + + return updates } } From 0cfb648ac858dfd8d6e1d0a19f854891a056d403 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 13:24:19 +0200 Subject: [PATCH 07/73] Rename debug_screen to debug, add heartbeat --- js/term/connection.js | 1 + js/term/{debug_screen.js => debug.js} | 21 ++++++++++++++++++--- js/term/index.js | 4 ++-- sass/pages/_term.scss | 26 ++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 5 deletions(-) rename js/term/{debug_screen.js => debug.js} (96%) diff --git a/js/term/connection.js b/js/term/connection.js index 4e48d34..d3cdf03 100644 --- a/js/term/connection.js +++ b/js/term/connection.js @@ -194,6 +194,7 @@ module.exports = class TermConnection extends EventEmitter { } heartbeat () { + this.emit('heartbeat') clearTimeout(this.heartbeatTimeout) this.heartbeatTimeout = setTimeout(() => this.onHeartbeatFail(), HEARTBEAT_TIME) } diff --git a/js/term/debug_screen.js b/js/term/debug.js similarity index 96% rename from js/term/debug_screen.js rename to js/term/debug.js index 680690c..fd7f5ae 100644 --- a/js/term/debug_screen.js +++ b/js/term/debug.js @@ -1,6 +1,6 @@ const { mk } = require('../utils') -module.exports = function attachDebugScreen (screen) { +module.exports = function attachDebugger (screen, connection) { const debugCanvas = mk('canvas') const ctx = debugCanvas.getContext('2d') @@ -215,17 +215,34 @@ module.exports = function attachDebugScreen (screen) { const toolbar = mk('div') toolbar.classList.add('debug-toolbar') let toolbarAttached = false + + const heartbeat = mk('div') + heartbeat.classList.add('heartbeat') + heartbeat.textContent = '❤' + toolbar.appendChild(heartbeat) + const dataDisplay = mk('div') dataDisplay.classList.add('data-display') toolbar.appendChild(dataDisplay) + const internalDisplay = mk('div') internalDisplay.classList.add('internal-display') toolbar.appendChild(internalDisplay) + toolbar.appendChild(drawInfo) + const buttons = mk('div') buttons.classList.add('toolbar-buttons') toolbar.appendChild(buttons) + // heartbeat + connection.on('heartbeat', () => { + heartbeat.classList.remove('beat') + window.requestAnimationFrame(() => { + heartbeat.classList.add('beat') + }) + }) + { const redraw = mk('button') redraw.textContent = 'Redraw' @@ -331,8 +348,6 @@ module.exports = function attachDebugScreen (screen) { value = true } - if (key === 'color') console.log(value) - if (key === 'bold') attrs.style += 'font-weight:bold;' if (key === 'italic') attrs.style += 'font-style:italic;' if (key === 'underline') attrs.style += 'text-decoration:underline;' diff --git a/js/term/index.js b/js/term/index.js index c6d8d82..0bbabaa 100644 --- a/js/term/index.js +++ b/js/term/index.js @@ -6,7 +6,7 @@ const TermConnection = require('./connection') const TermInput = require('./input') const TermUpload = require('./upload') const initSoftKeyboard = require('./soft_keyboard') -const attachDebugScreen = require('./debug_screen') +const attachDebugger = require('./debug') const initButtons = require('./buttons') /** Init the terminal sub-module - called from HTML */ @@ -102,7 +102,7 @@ module.exports = function (opts) { qs('#screen').appendChild(screen.canvas) initSoftKeyboard(screen, input) - if (attachDebugScreen) attachDebugScreen(screen) + if (attachDebugger) attachDebugger(screen, conn) let fullscreenIcon = {} // dummy let isFullscreen = false diff --git a/sass/pages/_term.scss b/sass/pages/_term.scss index 61e68f9..4b7bfa7 100644 --- a/sass/pages/_term.scss +++ b/sass/pages/_term.scss @@ -118,6 +118,32 @@ body.term { font-family: $screen-stack; font-size: 12px; white-space: normal; + + .heartbeat { + float: right; + font-family: $font-stack; + + &.beat { + animation-name: heartbeat-beat; + animation-duration: 2s; + animation-fill-mode: forwards; + + @keyframes heartbeat-beat { + 0% { + transform: scale(1); + animation-timing-function: ease-out; + } + 5% { + transform: scale(1.2); + animation-timing-function: linear; + } + 100% { + transform: scale(0); + opacity: 0; + } + } + } + } } } From 90a1676084990c7ba21c7683e7ee134372087ea1 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 13:51:07 +0200 Subject: [PATCH 08/73] Remove most references to screen in parser --- js/term/screen.js | 31 ++++- js/term/screen_parser.js | 262 ++++++++++++++++++--------------------- 2 files changed, 151 insertions(+), 142 deletions(-) diff --git a/js/term/screen.js b/js/term/screen.js index 17480f1..e9f039c 100644 --- a/js/term/screen.js +++ b/js/term/screen.js @@ -688,6 +688,10 @@ module.exports = class TermScreen extends EventEmitter { this.emit('button-labels', update.labels) break + case 'backdrop': + this.backgroundImage = update.image + break + case 'bell': this.beep() break @@ -697,7 +701,32 @@ module.exports = class TermScreen extends EventEmitter { break case 'content': - update.tempDoNotCommitUpstream() + const { frameX, frameY, frameWidth, frameHeight, cells } = update + + if (this._debug && this.window.debug) { + this._debug.pushFrame([frameX, frameY, frameWidth, frameHeight]) + } + + for (let cell = 0; cell < cells.length; cell++) { + let data = cells[cell] + + let cellXInFrame = cell % frameWidth + let cellYInFrame = Math.floor(cell / frameWidth) + let index = (frameY + cellYInFrame) * this.window.width + frameX + cellXInFrame + + this.screen[index] = data[0] + this.screenFG[index] = data[1] + this.screenBG[index] = data[2] + this.screenAttrs[index] = data[3] + } + + this.renderer.scheduleDraw('load', 16) + this.conn.emit('load') + this.emit('load') + break + + case 'full-load-complete': + this.emit('TEMP:hide-load-failed-msg') break default: diff --git a/js/term/screen_parser.js b/js/term/screen_parser.js index 46bbac9..090ea75 100644 --- a/js/term/screen_parser.js +++ b/js/term/screen_parser.js @@ -63,20 +63,10 @@ module.exports = class ScreenParser { } }) - // true if TermScreen#load was called at least once + // true if full content was loaded this.contentLoaded = false } - /** - * Hide the warning message about failed data load - */ - hideLoadFailedMsg () { - if (!this.contentLoaded) { - this.screen.emit('TEMP:hide-load-failed-msg') - this.contentLoaded = true - } - } - parseUpdate (str) { // console.log(`update ${str}`) // current index @@ -142,11 +132,6 @@ module.exports = class ScreenParser { else if (cursorStyle === 1) cursorStyle = 'line' else cursorStyle = 'bar' - if (this.screen.cursor.blinking !== cursorBlinking) { - this.screen.cursor.blinking = cursorBlinking - this.screen.renderer.resetCursorBlink() - } - const showButtons = !!(attributes & OPT_SHOW_BUTTONS) const showConfigLinks = !!(attributes & OPT_SHOW_CONFIG_LINKS) @@ -232,145 +217,140 @@ module.exports = class ScreenParser { }) } else if (topic === TOPIC_CONTENT) { // set screen content - let _ci = ci - - let tempDoNotCommitUpstream = () => { - let ci = _ci - const frameY = du(strArray[ci++]) - const frameX = du(strArray[ci++]) - const frameHeight = du(strArray[ci++]) - const frameWidth = du(strArray[ci++]) - - if (this.screen._debug && this.screen.window.debug) { - this.screen._debug.pushFrame([frameX, frameY, frameWidth, frameHeight]) + const frameY = du(strArray[ci++]) + const frameX = du(strArray[ci++]) + const frameHeight = du(strArray[ci++]) + const frameWidth = du(strArray[ci++]) + + // content + let fg = 7 + let bg = 0 + let attrs = 0 + let cell = 0 // cell index + let lastChar = ' ' + let frameLength = frameWidth * frameHeight + + const MASK_LINE_ATTR = ATTR_UNDERLINE | ATTR_OVERLINE | ATTR_STRIKE + const MASK_BLINK = ATTR_BLINK + + const cells = [] + + let pushCell = () => { + let hasFG = attrs & ATTR_FG + let hasBG = attrs & ATTR_BG + let cellFG = fg + let cellBG = bg + let cellAttrs = attrs + + // use 0,0 if no fg/bg. this is to match back-end implementation + // and allow leaving out fg/bg setting for cells with none + if (!hasFG) cellFG = 0 + if (!hasBG) cellBG = 0 + + // Remove blink attribute if it wouldn't have any effect + if ((cellAttrs & MASK_BLINK) && + ((lastChar === ' ' && ((cellAttrs & MASK_LINE_ATTR) === 0)) || // no line styles + (fg === bg && hasFG && hasBG) // invisible text + ) + ) { + cellAttrs ^= MASK_BLINK } - // content - let fg = 7 - let bg = 0 - let attrs = 0 - let cell = 0 // cell index - let lastChar = ' ' - let frameLength = frameWidth * frameHeight - - const MASK_LINE_ATTR = ATTR_UNDERLINE | ATTR_OVERLINE | ATTR_STRIKE - const MASK_BLINK = ATTR_BLINK - - let pushCell = () => { - // Remove blink attribute if it wouldn't have any effect - let myAttrs = attrs - let hasFG = attrs & ATTR_FG - let hasBG = attrs & ATTR_BG - let cellFG = fg - let cellBG = bg - - // use 0,0 if no fg/bg. this is to match back-end implementation - // and allow leaving out fg/bg setting for cells with none - if (!hasFG) cellFG = 0 - if (!hasBG) cellBG = 0 - - if ((myAttrs & MASK_BLINK) !== 0 && - ((lastChar === ' ' && ((myAttrs & MASK_LINE_ATTR) === 0)) || // no line styles - (fg === bg && hasFG && hasBG) // invisible text - ) - ) { - myAttrs ^= MASK_BLINK - } - // update blinking cells counter if blink state changed - if ((this.screen.screenAttrs[cell] & MASK_BLINK) !== (myAttrs & MASK_BLINK)) { - if (myAttrs & MASK_BLINK) this.screen.blinkingCellCount++ - else this.screen.blinkingCellCount-- - } - - let cellXInFrame = cell % frameWidth - let cellYInFrame = Math.floor(cell / frameWidth) - let index = (frameY + cellYInFrame) * this.screen.window.width + frameX + cellXInFrame - - // 8 dark system colors turn bright when bold - if ((myAttrs & ATTR_BOLD) && !(myAttrs & ATTR_FAINT) && hasFG && cellFG < 8) { - cellFG += 8 - } - - this.screen.screen[index] = lastChar - this.screen.screenFG[index] = cellFG - this.screen.screenBG[index] = cellBG - this.screen.screenAttrs[index] = myAttrs + // TODO: reimplement + /* + // update blinking cells counter if blink state changed + if ((this.screen.screenAttrs[cell] & MASK_BLINK) !== (cellAttrs & MASK_BLINK)) { + if (cellAttrs & MASK_BLINK) this.screen.blinkingCellCount++ + else this.screen.blinkingCellCount-- } + */ - while (ci < strArray.length && cell < frameLength) { - let character = strArray[ci++] - let charCode = character.codePointAt(0) - - let data, count - switch (charCode) { - case SEQ_REPEAT: - count = du(strArray[ci++]) - for (let j = 0; j < count; j++) { - pushCell() - if (++cell > frameLength) break - } - break - - case SEQ_SKIP: - cell += du(strArray[ci++]) - break - - case SEQ_SET_COLORS: - data = du(strArray[ci++]) - fg = data & 0xFF - bg = (data >> 8) & 0xFF - break - - case SEQ_SET_ATTRS: - data = du(strArray[ci++]) - attrs = data & 0xFFFF - break - - case SEQ_SET_ATTR_0: - attrs = 0 - break - - case SEQ_SET_FG: - data = du(strArray[ci++]) - if (data & 0x10000) { - data &= 0xFFF - data |= (du(strArray[ci++]) & 0xFFF) << 12 - data += 256 - } - fg = data - break - - case SEQ_SET_BG: - data = du(strArray[ci++]) - if (data & 0x10000) { - data &= 0xFFF - data |= (du(strArray[ci++]) & 0xFFF) << 12 - data += 256 - } - bg = data - break - - default: - if (charCode < 32) character = '\ufffd' - lastChar = character - pushCell() - cell++ - } + // 8 dark system colors turn bright when bold + if ((cellAttrs & ATTR_BOLD) && !(cellAttrs & ATTR_FAINT) && hasFG && cellFG < 8) { + cellFG += 8 } - if (this.screen.window.debug) console.log(`Blinky cells: ${this.screen.blinkingCellCount}`) + cells.push([lastChar, cellFG, cellBG, cellAttrs]) + } + + while (ci < strArray.length && cell < frameLength) { + let character = strArray[ci++] + let charCode = character.codePointAt(0) - this.screen.renderer.scheduleDraw('load', 16) - this.screen.conn.emit('load') + let data, count + switch (charCode) { + case SEQ_REPEAT: + count = du(strArray[ci++]) + for (let j = 0; j < count; j++) { + pushCell() + if (++cell > frameLength) break + } + break + + case SEQ_SKIP: + cell += du(strArray[ci++]) + break + + case SEQ_SET_COLORS: + data = du(strArray[ci++]) + fg = data & 0xFF + bg = (data >> 8) & 0xFF + break + + case SEQ_SET_ATTRS: + data = du(strArray[ci++]) + attrs = data & 0xFFFF + break + + case SEQ_SET_ATTR_0: + attrs = 0 + break + + case SEQ_SET_FG: + data = du(strArray[ci++]) + if (data & 0x10000) { + data &= 0xFFF + data |= (du(strArray[ci++]) & 0xFFF) << 12 + data += 256 + } + fg = data + break + + case SEQ_SET_BG: + data = du(strArray[ci++]) + if (data & 0x10000) { + data &= 0xFFF + data |= (du(strArray[ci++]) & 0xFFF) << 12 + data += 256 + } + bg = data + break + + default: + if (charCode < 32) character = '\ufffd' + lastChar = character + pushCell() + cell++ + } } + // TODO (see above) + // if (this.screen.window.debug) console.log(`Blinky cells: ${this.screen.blinkingCellCount}`) + updates.push({ topic: 'content', - tempDoNotCommitUpstream + frameX, + frameY, + frameWidth, + frameHeight, + cells }) } - if ((topics & 0x3B) !== 0) this.hideLoadFailedMsg() + if (topics & 0x3B && !this.contentLoaded) { + updates.push({ topic: 'full-load-complete' }) + this.contentLoaded = true + } } return updates From 174e6950d0d5bc0423639d7e74ee696b0e23a5a5 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 13:59:01 +0200 Subject: [PATCH 09/73] Add blinking cell counter back --- js/term/screen.js | 8 ++++++++ js/term/screen_parser.js | 14 +------------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/js/term/screen.js b/js/term/screen.js index e9f039c..08aa102 100644 --- a/js/term/screen.js +++ b/js/term/screen.js @@ -4,6 +4,7 @@ const { mk, qs } = require('../utils') const notify = require('../notif') const ScreenParser = require('./screen_parser') const ScreenRenderer = require('./screen_renderer') +const { ATTR_BLINK } = require('./screen_attr_bits') module.exports = class TermScreen extends EventEmitter { constructor () { @@ -714,12 +715,19 @@ module.exports = class TermScreen extends EventEmitter { let cellYInFrame = Math.floor(cell / frameWidth) let index = (frameY + cellYInFrame) * this.window.width + frameX + cellXInFrame + if (this.screenAttrs[index] & ATTR_BLINK !== data[3] & ATTR_BLINK) { + if (data[3] & ATTR_BLINK) this.blinkingCellCount++ + else this.blinkingCellCount-- + } + this.screen[index] = data[0] this.screenFG[index] = data[1] this.screenBG[index] = data[2] this.screenAttrs[index] = data[3] } + if (this.window.debug) console.log(`Blinking cells: ${this.blinkingCellCount}`) + this.renderer.scheduleDraw('load', 16) this.conn.emit('load') this.emit('load') diff --git a/js/term/screen_parser.js b/js/term/screen_parser.js index 090ea75..a80fbd9 100644 --- a/js/term/screen_parser.js +++ b/js/term/screen_parser.js @@ -69,13 +69,13 @@ module.exports = class ScreenParser { parseUpdate (str) { // console.log(`update ${str}`) + // current index let ci = 0 let strArray = Array.from ? Array.from(str) : str.split('') let text const topics = du(strArray[ci++]) - // this.screen.cursor.hanging = !!(attributes & (1 << 1)) let collectOneTerminatedString = () => { // TODO optimize this @@ -256,15 +256,6 @@ module.exports = class ScreenParser { cellAttrs ^= MASK_BLINK } - // TODO: reimplement - /* - // update blinking cells counter if blink state changed - if ((this.screen.screenAttrs[cell] & MASK_BLINK) !== (cellAttrs & MASK_BLINK)) { - if (cellAttrs & MASK_BLINK) this.screen.blinkingCellCount++ - else this.screen.blinkingCellCount-- - } - */ - // 8 dark system colors turn bright when bold if ((cellAttrs & ATTR_BOLD) && !(cellAttrs & ATTR_FAINT) && hasFG && cellFG < 8) { cellFG += 8 @@ -334,9 +325,6 @@ module.exports = class ScreenParser { } } - // TODO (see above) - // if (this.screen.window.debug) console.log(`Blinky cells: ${this.screen.blinkingCellCount}`) - updates.push({ topic: 'content', frameX, From 32c6246c10f7408ca35a636df6119588e031ca33 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 14:02:20 +0200 Subject: [PATCH 10/73] Fix cursor-hanging not updating properly --- js/term/screen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/term/screen.js b/js/term/screen.js index 08aa102..0d6557a 100644 --- a/js/term/screen.js +++ b/js/term/screen.js @@ -671,7 +671,7 @@ module.exports = class TermScreen extends EventEmitter { break case 'cursor': - if (this.cursor.x !== update.x || this.cursor.y !== update.y) { + if (this.cursor.x !== update.x || this.cursor.y !== update.y || this.cursor.hanging !== update.hanging) { this.cursor.x = update.x this.cursor.y = update.y this.cursor.hanging = update.hanging From a5aa536f89f13800772fbb51e21db8294c0adadb Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 14:07:55 +0200 Subject: [PATCH 11/73] =?UTF-8?q?ScreenParser=20is=20now=20standalone?= =?UTF-8?q?=E2=80=A6-able!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/term/screen.js | 6 +++++- js/term/screen_parser.js | 18 +++++------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/js/term/screen.js b/js/term/screen.js index 0d6557a..b482bd9 100644 --- a/js/term/screen.js +++ b/js/term/screen.js @@ -13,7 +13,7 @@ module.exports = class TermScreen extends EventEmitter { this.canvas = mk('canvas') this.ctx = this.canvas.getContext('2d') - this.parser = new ScreenParser(this) + this.parser = new ScreenParser() this.renderer = new ScreenRenderer(this) // debug screen handle @@ -737,6 +737,10 @@ module.exports = class TermScreen extends EventEmitter { this.emit('TEMP:hide-load-failed-msg') break + case 'notification': + this.showNotification(update.content) + break + default: console.log('Unhandled update', update) } diff --git a/js/term/screen_parser.js b/js/term/screen_parser.js index a80fbd9..c08ec1b 100644 --- a/js/term/screen_parser.js +++ b/js/term/screen_parser.js @@ -52,17 +52,7 @@ const OPT_REVERSE_VIDEO = (1 << 14) /* eslint-enable no-multi-spaces */ module.exports = class ScreenParser { - constructor (screen) { - // this.screen = screen - let didWarn = false - Object.defineProperty(this, 'screen', { - get () { - if (!didWarn) console.warn('Deprecated get ScreenParser#screen') - didWarn = true - return screen - } - }) - + constructor () { // true if full content was loaded this.contentLoaded = false } @@ -361,8 +351,10 @@ module.exports = class ScreenParser { break case 'G': - this.screen.showNotification(content) - break + return [{ + topic: 'notification', + content + }] default: console.warn(`Bad data message type; ignoring.\n${JSON.stringify(message)}`) From 3d93cd3690432ed39cf65887c3e008747740db4c Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 14:19:09 +0200 Subject: [PATCH 12/73] Add input queue --- js/term/connection.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/js/term/connection.js b/js/term/connection.js index d3cdf03..5e2c360 100644 --- a/js/term/connection.js +++ b/js/term/connection.js @@ -19,9 +19,10 @@ module.exports = class TermConnection extends EventEmitter { this.autoXoffTimeout = null this.reconnTimeout = null this.forceClosing = false + this.queue = [] try { - this.blobReader = new FileReader() + this.blobReader = new window.FileReader() this.blobReader.onload = (evt) => { this.onDecodedWSMessage(this.blobReader.result) } @@ -90,12 +91,14 @@ module.exports = class TermConnection extends EventEmitter { this.xoff = true this.autoXoffTimeout = setTimeout(() => { this.xoff = false + this.flushQueue() }, 250) break case '+': // console.log('xon'); this.xoff = false + this.flushQueue() clearTimeout(this.autoXoffTimeout) break @@ -143,7 +146,8 @@ module.exports = class TermConnection extends EventEmitter { } if (this.xoff) { // TODO queue - console.log("Can't send, flood control.") + console.log("Can't send, flood control. Queueing") + this.queue.push(message) return false } @@ -159,6 +163,12 @@ module.exports = class TermConnection extends EventEmitter { return true } + flushQueue () { + console.log('Flushing input queue') + for (let message of this.queue) this.send(message) + this.queue = [] + } + /** Safely close the socket */ closeSocket () { if (this.ws) { From f5bbb604682346e2418c80b4c2293545877ad5b7 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 14:23:05 +0200 Subject: [PATCH 13/73] Stop using pointers for button labels --- js/term/buttons.js | 11 ++++++++++- js/term/connection.js | 1 - js/term/index.js | 6 +----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/js/term/buttons.js b/js/term/buttons.js index 32813dd..e8a4cf8 100644 --- a/js/term/buttons.js +++ b/js/term/buttons.js @@ -60,5 +60,14 @@ module.exports = function initButtons (input) { } } - return { update, labels } + return { + update, + get labels () { + return labels + }, + set labels (value) { + labels = value + update() + } + } } diff --git a/js/term/connection.js b/js/term/connection.js index 5e2c360..d1c9ed4 100644 --- a/js/term/connection.js +++ b/js/term/connection.js @@ -145,7 +145,6 @@ module.exports = class TermConnection extends EventEmitter { return true // Simulate success } if (this.xoff) { - // TODO queue console.log("Can't send, flood control. Queueing") this.queue.push(message) return false diff --git a/js/term/index.js b/js/term/index.js index 0bbabaa..15d17ce 100644 --- a/js/term/index.js +++ b/js/term/index.js @@ -20,11 +20,7 @@ module.exports = function (opts) { input.termUpload = termUpload const buttons = initButtons(input) - screen.on('button-labels', labels => { - // TODO: don't use pointers for this - buttons.labels.splice(0, buttons.labels.length, ...labels) - buttons.update() - }) + screen.on('button-labels', labels => { buttons.labels = labels }) screen.on('TEMP:hide-load-failed-msg', () => { let scr = qs('#screen') From 094727c92237b2f2ba076af50b73c1633e26b610 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 14:29:45 +0200 Subject: [PATCH 14/73] Fix demo being loaded regardless of ESP_PROD --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 29ba3f4..d1c3104 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -10,7 +10,7 @@ let devtool = 'source-map' if (process.env.ESP_PROD) { // ignore demo - plugins.push(new webpack.IgnorePlugin(/\.\/demo(?:\.js)?$/)) + plugins.push(new webpack.IgnorePlugin(/(term|\.)\/demo(?:\.js)?$/)) // no source maps devtool = '' From 289cc5aaf2eb97cc45297e4987af0a2feb214216 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 8 Oct 2017 15:08:00 +0200 Subject: [PATCH 15/73] Remove all refs to external DOM objects in screen --- js/term/index.js | 32 +++++++++++++++++++++++--- js/term/screen.js | 57 ++++++++++------------------------------------- 2 files changed, 41 insertions(+), 48 deletions(-) diff --git a/js/term/index.js b/js/term/index.js index 15d17ce..d379e3c 100644 --- a/js/term/index.js +++ b/js/term/index.js @@ -1,3 +1,4 @@ +const $ = require('../lib/chibi') const { qs, mk } = require('../utils') const localize = require('../lang') const Notify = require('../notif') @@ -15,10 +16,35 @@ module.exports = function (opts) { const conn = new TermConnection(screen) const input = TermInput(conn, screen) const termUpload = TermUpload(conn, input, screen) - screen.input = input - screen.conn = conn input.termUpload = termUpload + screen.on('mousedown', (...args) => input.onMouseDown(...args)) + screen.on('mousemove', (...args) => input.onMouseMove(...args)) + screen.on('mouseup', (...args) => input.onMouseUp(...args)) + screen.on('mousewheel', (...args) => input.onMouseWheel(...args)) + screen.on('input-alts', (...args) => input.setAlts(...args)) + screen.on('mouse-mode', (...args) => input.setMouseMode(...args)) + + $.ready(() => { + const touchSelectMenu = qs('#touch-select-menu') + screen.on('show-touch-select-menu', (x, y) => { + let rect = touchSelectMenu.getBoundingClientRect() + x -= rect.width / 2 + y -= rect.height / 2 + + touchSelectMenu.classList.add('open') + touchSelectMenu.style.transform = `translate(${x}px,${y}px)` + }) + screen.on('hide-touch-select-menu', () => touchSelectMenu.classList.remove('open')) + + const copyButton = qs('#touch-select-copy-btn') + if (copyButton) { + copyButton.addEventListener('click', () => { + this.copySelectionToClipboard() + }) + } + }) + const buttons = initButtons(input) screen.on('button-labels', labels => { buttons.labels = labels }) @@ -62,7 +88,7 @@ module.exports = function (opts) { // console.log('*connect') showSplash({ title: localize('term_conn.waiting_content'), loading: true }) }) - conn.on('load', () => { + screen.on('load', () => { // console.log('*load') clearTimeout(showSplashTimeout) if (screen.window.statusScreen) screen.window.statusScreen = null diff --git a/js/term/screen.js b/js/term/screen.js index b482bd9..9627ccf 100644 --- a/js/term/screen.js +++ b/js/term/screen.js @@ -1,6 +1,5 @@ const EventEmitter = require('events') -const $ = require('../lib/chibi') -const { mk, qs } = require('../utils') +const { mk } = require('../utils') const notify = require('../notif') const ScreenParser = require('./screen_parser') const ScreenRenderer = require('./screen_renderer') @@ -25,22 +24,6 @@ module.exports = class TermScreen extends EventEmitter { console.warn('No AudioContext!') } - // dummy. Handle for Input - this.input = new Proxy({}, { - get () { - return () => console.warn('TermScreen#input not set!') - } - }) - // dummy. Handle for Conn - this.conn = new Proxy({}, { - get () { - return () => console.warn('TermScreen#conn not set!') - }, - set (a, b) { - return () => console.warn('TermScreen#conn not set!') - } - }) - this.cursor = { x: 0, y: 0, @@ -159,11 +142,11 @@ module.exports = class TermScreen extends EventEmitter { // bind event listeners this.canvas.addEventListener('mousedown', e => { + this.emit('hide-touch-select-menu') if ((this.selection.selectable || e.altKey) && e.button === 0) { selectStart(e.offsetX, e.offsetY) } else { - this.input.onMouseDown(...this.screenToGrid(e.offsetX, e.offsetY), - e.button + 1) + this.emit('mousedown', ...this.screenToGrid(e.offsetX, e.offsetY), e.button + 1) } }) @@ -218,19 +201,13 @@ module.exports = class TermScreen extends EventEmitter { selectEnd(...touchPosition) // selection ended; show touch select menu - let touchSelectMenu = qs('#touch-select-menu') - touchSelectMenu.classList.add('open') - let rect = touchSelectMenu.getBoundingClientRect() - // use middle position for x and one line above for y let selectionPos = this.gridToScreen( (this.selection.start[0] + this.selection.end[0]) / 2, this.selection.start[1] - 1 ) - selectionPos[0] -= rect.width / 2 - selectionPos[1] -= rect.height / 2 - touchSelectMenu.style.transform = `translate(${selectionPos[0]}px, ${ - selectionPos[1]}px)` + + this.emit('show-touch-select-menu', selectionPos[0], selectionPos[1]) } if (!touchDidMove && !this.mouseMode.clicks) { @@ -249,7 +226,7 @@ module.exports = class TermScreen extends EventEmitter { // selection is not empty // reset selection this.selection.start = this.selection.end = [0, 0] - qs('#touch-select-menu').classList.remove('open') + this.emit('hide-touch-select-menu') this.renderer.scheduleDraw('select-reset') } else { e.preventDefault() @@ -257,24 +234,15 @@ module.exports = class TermScreen extends EventEmitter { } }) - $.ready(() => { - let copyButton = qs('#touch-select-copy-btn') - if (copyButton) { - copyButton.addEventListener('click', () => { - this.copySelectionToClipboard() - }) - } - }) - this.canvas.addEventListener('mousemove', e => { if (!selecting) { - this.input.onMouseMove(...this.screenToGrid(e.offsetX, e.offsetY)) + this.emit('mousemove', ...this.screenToGrid(e.offsetX, e.offsetY)) } }) this.canvas.addEventListener('mouseup', e => { if (!selecting) { - this.input.onMouseUp(...this.screenToGrid(e.offsetX, e.offsetY), + this.emit('mouseup', ...this.screenToGrid(e.offsetX, e.offsetY), e.button + 1) } }) @@ -284,12 +252,12 @@ module.exports = class TermScreen extends EventEmitter { if (this.mouseMode.clicks) { if (Math.abs(e.wheelDeltaY) === 120) { // mouse wheel scrolling - this.input.onMouseWheel(...this.screenToGrid(e.offsetX, e.offsetY), e.deltaY > 0 ? 1 : -1) + this.emit('mousewheel', ...this.screenToGrid(e.offsetX, e.offsetY), e.deltaY > 0 ? 1 : -1) } else { // smooth scrolling aggregateWheelDelta -= e.wheelDeltaY if (Math.abs(aggregateWheelDelta) >= 40) { - this.input.onMouseWheel(...this.screenToGrid(e.offsetX, e.offsetY), aggregateWheelDelta > 0 ? 1 : -1) + this.emit('mousewheel', ...this.screenToGrid(e.offsetX, e.offsetY), aggregateWheelDelta > 0 ? 1 : -1) aggregateWheelDelta = 0 } } @@ -651,10 +619,10 @@ module.exports = class TermScreen extends EventEmitter { this.renderer.loadTheme(update.theme) this.renderer.setDefaultColors(update.defFG, update.defBG) this.cursor.visible = update.cursorVisible - this.input.setAlts(...update.inputAlts) + this.emit('input-alts', ...update.inputAlts) this.mouseMode.clicks = update.trackMouseClicks this.mouseMode.movement = update.trackMouseMovement - this.input.setMouseMode(update.trackMouseClicks, update.trackMouseMovement) + this.emit('mouse-mode', update.trackMouseClicks, update.trackMouseMovement) this.selection.setSelectable(!update.trackMouseClicks && !update.trackMouseMovement) if (this.cursor.blinking !== update.cursorBlinking) { this.cursor.blinking = update.cursorBlinking @@ -729,7 +697,6 @@ module.exports = class TermScreen extends EventEmitter { if (this.window.debug) console.log(`Blinking cells: ${this.blinkingCellCount}`) this.renderer.scheduleDraw('load', 16) - this.conn.emit('load') this.emit('load') break From c3f6b4d85148e8334f9413a91965bf46fba1f673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 8 Oct 2017 15:12:32 +0200 Subject: [PATCH 16/73] add default terminal title if load fails --- pages/term.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/term.php b/pages/term.php index 9a2170d..7f82909 100644 --- a/pages/term.php +++ b/pages/term.php @@ -44,7 +44,7 @@ -

+

ESPTerm

@@ -59,7 +59,7 @@
-
+