Add basic terminal echoing input

cpsdqs/unified-input
cpsdqs 7 years ago
parent 259b9f05e9
commit c7a5215fe1
Signed by untrusted user: cpsdqs
GPG Key ID: 3F59586BB7448DD1
  1. 1
      base.php
  2. 1
      build.sh
  3. 202
      js/demo.js
  4. 4
      js/term_conn.js
  5. 1
      pages/_head.php

File diff suppressed because one or more lines are too long

@ -20,6 +20,7 @@ npm run babel -- -o out/js/app.js --source-maps js/lib \
js/appcommon.js \
js/lang.js \
js/wifi.js \
js/demo.js \
js/term_* \
js/debug_screen.js \
js/soft_keyboard.js \

@ -0,0 +1,202 @@
const TERM_DEFAULT_STYLE = 7
const TERM_MIN_DRAW_DELAY = 10
class ScrollingTerminal {
constructor (screen) {
this.width = 80
this.height = 25
this.termScreen = screen
this.reset()
this._lastLoad = Date.now()
this.termScreen.load(this.serialize(), 0)
}
reset () {
this.style = TERM_DEFAULT_STYLE
this.cursor = { x: 0, y: 0, style: 1 }
this.trackMouse = false
this.currentSequence = 0
this.sequence = ''
this.clear()
}
clear () {
this.screen = []
for (let i = 0; i < this.width * this.height; i++) {
this.screen.push([' ', this.style])
}
}
scroll () {
this.screen.splice(0, this.width)
for (let i = 0; i < this.width; i++) {
this.screen.push([' ', TERM_DEFAULT_STYLE])
}
this.cursor.y--
}
newLine () {
this.cursor.y++
if (this.cursor.y >= this.height) this.scroll()
}
writeChar (character) {
this.screen[this.cursor.y * this.width + this.cursor.x] = [character, this.style]
this.cursor.x++
if (this.cursor.x >= this.width) {
this.cursor.x = 0
this.newLine()
}
}
deleteChar () {
this.cursor.x--
if (this.cursor.x < 0) {
this.cursor.x = this.width - 1
this.cursor.y = Math.max(0, this.cursor.y - 1)
}
this.screen.splice((this.cursor.y + 1) * this.width, 0, [' ', TERM_DEFAULT_STYLE])
this.screen.splice(this.cursor.y * this.width + this.cursor.x, 1)
}
clampCursor () {
if (this.cursor.x < 0) this.cursor.x = 0
if (this.cursor.y < 0) this.cursor.y = 0
if (this.cursor.x > this.width - 1) this.cursor.x = this.width - 1
if (this.cursor.y > this.height - 1) this.cursor.y = this.height - 1
}
applySequence (sequence) {
if (sequence[0] === '[') {
let type = sequence[sequence.length - 1]
let content = sequence.substring(1, sequence.length - 1)
let numbers = content ? content.split(';').map(i => +i.replace(/\D/g, '')) : []
let numOr1 = numbers.length ? numbers[0] : 1
if (type === 'H') {
this.cursor.x = (numbers[0] | 0) - 1
this.cursor.y = (numbers[1] | 0) - 1
this.clampCursor()
} else if (type >= 'A' && type <= 'D') {
this.cursor[type <= 'B' ? 'y' : 'x'] += ((type === 'B' || type === 'C') ? 1 : -1) * numOr1
this.clampCursor()
} else if (type === 'E' || type === 'F') {
this.cursor.x = 0
this.cursor.y += (type === 'E' ? 1 : -1) * numOr1
this.clampCursor()
} else if (type === 'G') {
this.cursor.x = numOr1 - 1
this.clampCursor()
} else if (type === 'q') this.cursor.style = numOr1
else if (type === 'm') {
if (!numbers.length) {
this.style = TERM_DEFAULT_STYLE
return
}
let type = numbers[0]
if (type === 1) this.style |= 1 << 16 // bold
else if (type === 2) this.style |= 1 << 1 << 16 // faint
else if (type === 3) this.style |= 1 << 2 << 16 // italic
else if (type === 4) this.style |= 1 << 3 << 16 // underline
else if (type === 5 || type === 6) this.style |= 1 << 4 << 16 // blink
else if (type === 7) {
// invert
this.style = (this.style & 0xFF0000) | ((this.style >> 8) & 0xFF) | ((this.style & 0xFF) << 8)
} else if (type === 9) this.style |= 1 << 6 << 16 // strike
else if (type === 20) this.style |= 1 << 5 << 16 // fraktur
else if (type >= 30 && type <= 38) this.style = (this.style & 0xFFFF00) | (type % 10)
else if (type >= 40 && type <= 48) this.style = (this.style & 0xFF00FF) | ((type % 10) << 8)
else if (type === 39) this.style = (this.style & 0xFFFF00) | 7
else if (type === 49) this.style = (this.style & 0xFF00FF) | (7 << 8)
else if (type >= 90 && type <= 98) this.style = (this.style & 0xFFFF00) | ((type % 10) + 8)
else if (type >= 100 && type <= 108) this.style = (this.style & 0xFF00FF) | (((type % 10) + 8) << 8)
else if (type === 38 || type === 48) {
if (numbers[1] === 5) {
let color = (numbers[2] | 0) & 0xFF
if (type === 38) this.style = (this.style & 0xFFFF00) | color
if (type === 48) this.style = (this.style & 0xFF00FF) | (color << 8)
}
}
}
}
}
write (text) {
for (let character of text) {
let code = character.codePointAt(0)
if (code === 0x1b) this.currentSequence = 1
else if (this.currentSequence === 1 && character === '[') {
this.currentSequence = 2
this.sequence += '['
} else if (this.currentSequence && character.match(/[\x40-\x7e]/)) {
this.applySequence(this.sequence + character)
this.currentSequence = 0
this.sequence = ''
} else if (this.currentSequence > 1) this.sequence += character
else if (this.currentSequence === 1) {
// something something nothing
this.currentSequence = 0
this.writeChar(character)
} else if (code === 0x07) this.termScreen.load('B')
else if (code === 0x08) this.deleteChar()
else if (code === 0x0a) this.newLine()
else if (code === 0x0d) this.cursor.x = 0
else this.writeChar(character)
}
this.scheduleLoad()
}
serialize () {
let serialized = 'S'
serialized += encode2B(this.height) + encode2B(this.width)
serialized += encode2B(this.cursor.y) + encode2B(this.cursor.x)
let attributes = 1 // cursor always visible
attributes |= (3 << 5) * +this.trackMouse // track mouse controls both
attributes |= 3 << 7 // buttons/links always visible
attributes |= (this.cursor.shape << 9)
serialized += encode3B(attributes)
let lastStyle = null
for (let cell of this.screen) {
if (cell[1] !== lastStyle) {
let foreground = cell[1] & 0xFF
let background = (cell[1] >> 8) & 0xFF
let attributes = (cell[1] >> 16) & 0xFF
let setForeground = foreground !== (lastStyle & 0xFF)
let setBackground = background !== ((lastStyle >> 8) & 0xFF)
let setAttributes = attributes !== ((lastStyle >> 16) & 0xFF)
if (setForeground && setBackground) serialized += '\x03' + encode3B(cell[1] & 0xFFFF)
else if (setForeground) serialized += '\x05' + encode2B(foreground)
else if (setBackground) serialized += '\x06' + encode2B(background)
if (setAttributes) serialized += '\x04' + encode2B(attributes)
lastStyle = cell[1]
}
serialized += cell[0]
}
return serialized
}
scheduleLoad () {
clearInterval(this._scheduledLoad)
if (this._lastLoad < Date.now() - TERM_MIN_DRAW_DELAY) {
this.termScreen.load(this.serialize())
} else {
this._scheduledLoad = setTimeout(() => {
this.termScreen.load(this.serialize())
}, TERM_MIN_DRAW_DELAY - this._lastLoad)
}
}
}
window.demoInterface = {
input (data) {
let type = data[0]
let content = data.substr(1)
if (type === 's') {
this.terminal.write(content)
} else if (type === 'b') {
let button = content.charCodeAt(0)
console.log(`button ${button} pressed`)
} else if (type === 'm' || type === 'p' || type === 'r') {
console.log(JSON.stringify(data))
}
},
init (screen) {
this.terminal = new ScrollingTerminal(screen)
}
}

@ -67,7 +67,7 @@ window.Conn = function (screen) {
function doSend (message) {
if (_demo) {
console.log('TX: ', message)
window.demoInterface.input(message)
return true // Simulate success
}
if (xoff) {
@ -91,7 +91,7 @@ window.Conn = function (screen) {
function init () {
if (window._demo) {
console.log('Demo mode!')
screen.load(_demo_screen)
demoInterface.init(screen)
showPage()
return
}

@ -10,7 +10,6 @@
<script>
var _root = <?= JS_WEB_ROOT ?>;
var _demo = <?= (int)ESP_DEMO ?>;
<?php if($_GET['page']=='term'): ?>var _demo_screen = <?= ESP_DEMO ? DEMO_SCREEN : 0 ?>;<?php endif; ?>
<?php if($_GET['page']=='cfg_wifi'): ?>var _demo_aps = <?= ESP_DEMO ? json_encode(DEMO_APS) : '""' ?>;<?php endif; ?>
</script>
</head>

Loading…
Cancel
Save