Merge branch 'work'

pull/2/head
Ondřej Hruška 7 years ago
commit e3b21adc45
  1. 8
      _build_js.sh
  2. 2
      _debug_replacements.php
  3. 22
      base.php
  4. 2
      js/appcommon.js
  5. 181
      js/demo.js
  6. 16
      js/soft_keyboard.js
  7. 4
      js/term.js
  8. 4
      js/term_conn.js
  9. 52
      js/term_input.js
  10. 25
      js/term_screen.js
  11. 8
      js/term_upload.js
  12. 2
      js/wifi.js

@ -5,17 +5,13 @@ mkdir -p out/js
echo 'Generating lang.js...'
php ./dump_js_lang.php
if [[ $ESP_DEMO ]]; then
demofile=js/demo.js
else
demofile=
fi
echo 'Processing JS...'
if [[ $ESP_PROD ]]; then
smarg=
demofile=
else
smarg=--source-maps
demofile=js/demo.js
fi
npm run babel -- -o "out/js/app.$FRONT_END_HASH.js" ${smarg} js/lib \

@ -83,7 +83,7 @@ return [
'term_height' => '25',
'default_bg' => '0',
'default_fg' => '7',
'show_buttons' => '0',
'show_buttons' => '1',
'show_config_links' => '1',
'uart_baud' => 115200,

@ -12,19 +12,27 @@ if (!empty($argv[1])) {
parse_str($argv[1], $_GET);
}
if (!file_exists(__DIR__ . '/_env.php')) {
die("Copy <b>_env.php.example</b> to <b>_env.php</b> and check the settings inside!");
}
define('GIT_HASH', trim(shell_exec('git rev-parse --short HEAD')));
require_once __DIR__ . '/_env.php';
$prod = defined('STDIN');
define('DEBUG', !$prod);
$root = DEBUG ? json_encode(ESP_IP) : 'location.host';
// Resolve hostname for ajax etc
$root = 'location.host';
if (!file_exists(__DIR__ . '/_env.php')) {
if (DEBUG) {
die("No _env.php found! Copy _env.php.example</b> to <b>_env.php</b> and check the settings inside!");
}
} else {
if (DEBUG) {
require_once __DIR__ . '/_env.php';
$root = json_encode(ESP_IP);
}
}
define('JS_WEB_ROOT', $root);
define('ESP_DEMO', (bool)getenv('ESP_DEMO'));
if (ESP_DEMO) {
define('DEMO_APS', <<<APS

@ -97,7 +97,7 @@ $.ready(function () {
Modal.init()
Notify.init()
// remove tabindixes from h2 if wide
// remove tabindices from h2 if wide
if (window.innerWidth > 550) {
$('.Box h2').forEach(function (x) {
x.removeAttribute('tabindex')

@ -82,7 +82,10 @@ class ANSIParser {
// something something nothing
this.currentSequence = 0
this.handler('write', character)
} else if (code === 0x07) this.handler('bell')
} else if (code < 0x03) this.handler('_null')
else if (code === 0x03) this.handler('sigint')
else if (code <= 0x06) this.handler('_null')
else if (code === 0x07) this.handler('bell')
else if (code === 0x08) this.handler('back')
else if (code === 0x0a) this.handler('new-line')
else if (code === 0x0d) this.handler('return')
@ -203,8 +206,8 @@ class ScrollingTerminal {
} else if (action === 'return') {
this.cursor.x = 0
} else if (action === 'set-cursor') {
this.cursor.x = args[0]
this.cursor.y = args[1]
this.cursor.x = args[1]
this.cursor.y = args[0]
this.clampCursor()
} else if (action === 'move-cursor-y') {
this.cursor.y += args[0]
@ -370,7 +373,8 @@ let demoData = {
}
setTimeout(loop, 200)
}
}
},
mouseReceiver: null
}
let demoshIndex = {
@ -613,6 +617,115 @@ let demoshIndex = {
this.destroy()
}
},
mouse: class ShowMouse extends Process {
constructor (shell) {
super()
this.shell = shell
}
run () {
this.shell.terminal.trackMouse = true
demoData.mouseReceiver = this
this.randomData = []
this.highlighted = {}
let characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
for (let i = 0; i < 23; i++) {
let line = ''
for (let j = 0; j < 79; j++) {
line += characters[Math.floor(characters.length * Math.random())]
}
this.randomData.push(line)
}
this.scrollOffset = 0
this.render()
}
render () {
this.emit('write', '\x1b[m\x1b[2J\x1b[1;1H')
this.emit('write', '\x1b[97m\x1b[1mMouse Demo\r\n\x1b[mMouse movement, clicking and scrolling!')
// render random data for scrolling
for (let y = 0; y < 23; y++) {
let index = y + this.scrollOffset
// proper modulo:
index = ((index % this.randomData.length) + this.randomData.length) % this.randomData.length
let line = this.randomData[index]
let lineData = `\x1b[${3 + y};1H\x1b[38;5;239m`
for (let x in line) {
if (this.highlighted[(y + 2) * 80 + (+x)]) lineData += '\x1b[97m'
lineData += line[x]
if (this.highlighted[(y + 2) * 80 + (+x)]) lineData += '\x1b[38;5;239m'
}
this.emit('write', lineData)
}
// move cursor to mouse
if (this.mouse) {
this.emit('write', `\x1b[${this.mouse.y + 1};${this.mouse.x + 1}H`)
}
}
mouseMove (x, y) {
this.mouse = { x, y }
this.render()
}
mouseDown (x, y, button) {
if (button === 4) this.scrollOffset--
else if (button === 5) this.scrollOffset++
else this.highlighted[y * 80 + x] = !this.highlighted[y * 80 + x]
this.render()
}
mouseUp (x, y, button) {}
destroy () {
this.shell.terminal.write('\x1b[2J\x1b[1;1H')
this.shell.terminal.trackMouse = false
if (demoData.mouseReceiver === this) demoData.mouseReceiver = null
super.destroy()
}
},
sudo: class Sudo extends Process {
run (...args) {
if (args.length === 0) this.emit('write', '\x1b[31musage: sudo <command>\x1b[0m\n')
else if (args.length === 4 && args.join(' ').toLowerCase() === 'make me a sandwich') {
const b = '\x1b[33m'
const r = '\x1b[0m'
const l = '\x1b[32m'
const c = '\x1b[38;5;229m'
const h = '\x1b[38;5;225m'
this.emit('write',
` ${b}_.---._\r\n` +
` _.-~ ~-._\r\n` +
` _.-~ ~-._\r\n` +
` _.-~ ~---._\r\n` +
` _.-~ ~\\\r\n` +
` .-~ _.;\r\n` +
` :-._ _.-~ ./\r\n` +
` \`-._~-._ _..__.-~ _.-~\r\n` +
` ${c}/ ${b}~-._~-._ / .__..--${c}~-${l}---._\r\n` +
`${c} \\_____(_${b};-._\\. _.-~_/${c} ~)${l}.. . \\\r\n` +
`${l} /(_____ ${b}\\\`--...--~_.-~${c}______..-+${l}_______)\r\n` +
`${l} .(_________/${b}\`--...--~/${l} _/ ${h} ${b}/\\\r\n` +
`${b} /-._${h} \\_ ${l}(___./_..-~${h}__.....${b}__..-~./\r\n` +
`${b} \`-._~-._${h} ~\\--------~ .-~${b}_..__.-~ _.-~\r\n` +
`${b} ~-._~-._ ${h}~---------\` ${b}/ .__..--~\r\n` +
`${b} ~-._\\. _.-~_/\r\n` +
`${b} \\\`--...--~_.-~\r\n` +
`${b} \`--...--~${r}\r\n`)
} else {
this.emit('exec', args.join(' '))
return
}
this.destroy()
}
},
make: class Make extends Process {
run (...args) {
if (args.length === 0) this.emit('write', '\x1b[31mmake: *** No targets specified. Stop.\x1b[0m\r\n')
else if (args.length === 3 && args.join(' ').toLowerCase() === 'me a sandwich') {
this.emit('write', '\x1b[31mmake: me a sandwich : Permission denied\x1b[0m\r\n')
} else {
this.emit('write', `\x1b[31mmake: *** No rule to make target '${args.join(' ').toLowerCase()}'. Stop.\x1b[0m\r\n`)
}
this.destroy()
}
},
pwd: '/this/is/a/demo\r\n',
cd: '\x1b[38;5;239mNo directories to change to\r\n',
whoami: `${window.navigator.userAgent}\r\n`,
@ -624,7 +737,13 @@ let demoshIndex = {
mv: '\x1b[38;5;239mNothing to move because this is a demo.\r\n',
ln: '\x1b[38;5;239mNothing to link because this is a demo.\r\n',
touch: '\x1b[38;5;239mNothing to touch\r\n',
exit: '\x1b[38;5;239mNowhere to go\r\n'
exit: '\x1b[38;5;239mNowhere to go\r\n',
github: class GoToGithub extends Process {
run () {
window.open('https://github.com/espterm/espterm-firmware')
this.destroy()
}
}
}
class DemoShell {
@ -632,7 +751,8 @@ class DemoShell {
this.terminal = terminal
this.terminal.reset()
this.parser = new ANSIParser((...args) => this.handleParsed(...args))
this.input = ''
this.history = []
this.historyIndex = 0
this.cursorPos = 0
this.child = null
this.index = demoshIndex
@ -651,38 +771,53 @@ class DemoShell {
this.terminal.write('\x1b[34;1mdemosh \x1b[m')
if (!success) this.terminal.write('\x1b[31m')
this.terminal.write('$ \x1b[m')
this.input = ''
this.history.unshift('')
this.cursorPos = 0
}
copyFromHistoryIndex () {
if (!this.historyIndex) return
let current = this.history[this.historyIndex]
this.history[0] = current
this.historyIndex = 0
}
handleParsed (action, ...args) {
this.terminal.write('\b\x1b[P'.repeat(this.cursorPos))
if (action === 'write') {
this.input = this.input.substr(0, this.cursorPos) + args[0] + this.input.substr(this.cursorPos)
this.copyFromHistoryIndex()
this.history[0] = this.history[0].substr(0, this.cursorPos) + args[0] + this.history[0].substr(this.cursorPos)
this.cursorPos++
} else if (action === 'back') {
this.input = this.input.substr(0, this.cursorPos - 1) + this.input.substr(this.cursorPos)
this.copyFromHistoryIndex()
this.history[0] = this.history[0].substr(0, this.cursorPos - 1) + this.history[0].substr(this.cursorPos)
this.cursorPos--
if (this.cursorPos < 0) this.cursorPos = 0
} else if (action === 'move-cursor-x') {
this.cursorPos = Math.max(0, Math.min(this.input.length, this.cursorPos + args[0]))
this.cursorPos = Math.max(0, Math.min(this.history[0].length, this.cursorPos + args[0]))
} else if (action === 'delete-line') {
this.input = ''
this.copyFromHistoryIndex()
this.history[0] = ''
this.cursorPos = 0
} else if (action === 'delete-word') {
let words = this.input.substr(0, this.cursorPos).split(' ')
this.copyFromHistoryIndex()
let words = this.history[0].substr(0, this.cursorPos).split(' ')
words.pop()
this.input = words.join(' ') + this.input.substr(this.cursorPos)
this.history[0] = words.join(' ') + this.history[0].substr(this.cursorPos)
this.cursorPos = words.join(' ').length
} else if (action === 'move-cursor-y') {
this.historyIndex -= args[0]
if (this.historyIndex < 0) this.historyIndex = 0
if (this.historyIndex >= this.history.length) this.historyIndex = this.history.length - 1
this.cursorPos = this.history[this.historyIndex].length
}
this.terminal.write(this.input)
this.terminal.write('\b'.repeat(this.input.length))
this.terminal.write(this.history[this.historyIndex])
this.terminal.write('\b'.repeat(this.history[this.historyIndex].length))
this.terminal.moveForward(this.cursorPos)
this.terminal.write('') // dummy. Apply the moveFoward
if (action === 'return') {
this.terminal.write('\r\n')
this.parse(this.input)
this.parse(this.history[this.historyIndex])
}
}
parse (input) {
@ -719,9 +854,12 @@ class DemoShell {
if (Process instanceof Function) {
this.child = new Process(this)
let write = data => this.terminal.write(data)
let exec = line => this.run(line)
this.child.on('write', write)
this.child.on('exec', exec)
this.child.on('exit', code => {
if (this.child) this.child.off('write', write)
if (this.child) this.child.off('exec', exec)
this.child = null
this.prompt(!code)
})
@ -748,7 +886,16 @@ window.demoInterface = {
else if (action instanceof Function) action(this.terminal, this.shell)
}
} else if (type === 'm' || type === 'p' || type === 'r') {
console.log(JSON.stringify(data))
let row = parse2B(content, 0)
let column = parse2B(content, 2)
let button = parse2B(content, 4)
let modifiers = parse2B(content, 6)
if (demoData.mouseReceiver) {
if (type === 'm') demoData.mouseReceiver.mouseMove(column, row, button, modifiers)
else if (type === 'p') demoData.mouseReceiver.mouseDown(column, row, button, modifiers)
else if (type === 'r') demoData.mouseReceiver.mouseUp(column, row, button, modifiers)
}
}
},
init (screen) {

@ -4,6 +4,9 @@ window.initSoftKeyboard = function (screen, input) {
let keyboardOpen = false
// moves the input to where the cursor is on the canvas.
// this is because most browsers will always scroll to wherever the focused
// input is
let updateInputPosition = function () {
if (!keyboardOpen) return
@ -20,16 +23,9 @@ window.initSoftKeyboard = function (screen, input) {
screen.on('cursor-moved', updateInputPosition)
let kbOpen = function (open) {
keyboardOpen = open
updateInputPosition()
if (open) keyInput.focus()
else keyInput.blur()
}
qs('#term-kb-open').addEventListener('click', function () {
kbOpen(true)
return false
qs('#term-kb-open').addEventListener('click', e => {
e.preventDefault()
keyInput.focus()
})
// Chrome for Android doesn't send proper keydown/keypress events with

@ -1,7 +1,5 @@
/** Init the terminal sub-module - called from HTML */
window.termInit = function (opts) {
let { labels, theme, allFn } = opts
window.termInit = function ({ labels, theme, allFn }) {
const screen = new TermScreen()
const conn = Conn(screen)
const input = Input(conn)

@ -137,8 +137,8 @@ window.Conn = function (screen) {
return {
ws: null,
init: init,
init,
send: doSend,
canSend: canSend // check flood control
canSend // check flood control
}
}

@ -32,7 +32,7 @@ window.Input = function (conn) {
/** Send a button event */
function sendBtnMsg (n) {
conn.send('b' + Chr(n))
conn.send('b' + String.fromCharCode(n))
}
/** Fn alt choice for key message */
@ -50,7 +50,7 @@ window.Input = function (conn) {
return cfg.np_alt ? alt : normal
}
function _bindFnKeys (allFn) {
function bindFnKeys (allFn) {
const keymap = {
'tab': '\x09',
'backspace': '\x08',
@ -106,7 +106,9 @@ window.Input = function (conn) {
'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)
// we don't implement numlock key (should change in numpad_alt mode,
// but it's even more useless than the rest and also has the side
// effect of changing the user's numlock state)
}
const blacklist = [
@ -139,9 +141,7 @@ window.Input = function (conn) {
}
/** Bind/rebind key messages */
function _initKeys (opts) {
let { allFn } = opts
function initKeys ({ allFn }) {
// This takes care of text characters typed
window.addEventListener('keypress', function (evt) {
if (cfg.no_keys) return
@ -188,11 +188,11 @@ window.Input = function (conn) {
bind('⌥+right', '\x1bf') // ⌥→ to go forward one word (^[f)
bind('⌘+left', '\x01') // ⌘← to go to the beginning of a line (^A)
bind('⌘+right', '\x05') // ⌘→ to go to the end of a line (^E)
bind('⌥+backspace', '\x17') // ⌥⌫ to delete a word (^W, I think)
bind('⌘+backspace', '\x15') // ⌘⌫ to delete to the beginning of a line (possibly ^U)
bind('⌥+backspace', '\x17') // ⌥⌫ to delete a word (^W)
bind('⌘+backspace', '\x15') // ⌘⌫ to delete to the beginning of a line (^U)
/* eslint-enable */
_bindFnKeys(allFn)
bindFnKeys(allFn)
}
// mouse button states
@ -202,23 +202,23 @@ window.Input = function (conn) {
/** Init the Input module */
function init (opts) {
_initKeys(opts)
initKeys(opts)
// Button presses
$('#action-buttons button').forEach(function (s) {
s.addEventListener('click', function () {
$('#action-buttons button').forEach(s => {
s.addEventListener('click', function (evt) {
sendBtnMsg(+this.dataset['n'])
})
})
// global mouse state tracking - for motion reporting
window.addEventListener('mousedown', function (evt) {
window.addEventListener('mousedown', evt => {
if (evt.button === 0) mb1 = 1
if (evt.button === 1) mb2 = 1
if (evt.button === 2) mb3 = 1
})
window.addEventListener('mouseup', function (evt) {
window.addEventListener('mouseup', evt => {
if (evt.button === 0) mb1 = 0
if (evt.button === 1) mb2 = 0
if (evt.button === 2) mb3 = 0
@ -235,7 +235,7 @@ window.Input = function (conn) {
return {
/** Init the Input module */
init: init,
init,
/** Send a literal string message */
sendString: sendStrMsg,
@ -249,24 +249,24 @@ window.Input = function (conn) {
cfg.crlf_mode = crlf
// rebind keys - codes have changed
_bindFnKeys()
bindFnKeys()
}
},
setMouseMode: function (click, move) {
setMouseMode (click, move) {
cfg.mt_click = click
cfg.mt_move = move
},
// Mouse events
onMouseMove: function (x, y) {
onMouseMove (x, y) {
if (!cfg.mt_move) return
const b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0
const m = packModifiersForMouse()
conn.send('m' + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m))
},
onMouseDown: function (x, y, b) {
onMouseDown (x, y, b) {
if (!cfg.mt_click) return
if (b > 3 || b < 1) return
const m = packModifiersForMouse()
@ -274,7 +274,7 @@ window.Input = function (conn) {
// console.log("B ",b," M ",m);
},
onMouseUp: function (x, y, b) {
onMouseUp (x, y, b) {
if (!cfg.mt_click) return
if (b > 3 || b < 1) return
const m = packModifiersForMouse()
@ -282,7 +282,7 @@ window.Input = function (conn) {
// console.log("B ",b," M ",m);
},
onMouseWheel: function (x, y, dir) {
onMouseWheel (x, y, dir) {
if (!cfg.mt_click) return
// -1 ... btn 4 (away from user)
// +1 ... btn 5 (towards user)
@ -292,11 +292,11 @@ window.Input = function (conn) {
// console.log("B ",b," M ",m);
},
mouseTracksClicks: function () {
return cfg.mt_click
},
blockKeys: function (yes) {
/**
* Prevent capturing keys. This is used for text input
* modals on the terminal screen
*/
blockKeys (yes) {
cfg.no_keys = yes
}
}

@ -524,7 +524,8 @@ window.TermScreen = class TermScreen {
* Updates the canvas size if it changed
*/
updateSize () {
this._window.devicePixelRatio = this._windowScale * (window.devicePixelRatio || 1)
// see below (this is just updating it)
this._window.devicePixelRatio = Math.round(this._windowScale * (window.devicePixelRatio || 1) * 2) / 2
let didChange = false
for (let key in this.windowState) {
@ -573,7 +574,8 @@ window.TermScreen = class TermScreen {
// store new window scale
this._windowScale = realWidth / (width * cellSize.width)
let devicePixelRatio = this._window.devicePixelRatio = this._windowScale * window.devicePixelRatio
// the DPR must be rounded to a very nice value to prevent gaps between cells
let devicePixelRatio = this._window.devicePixelRatio = Math.round(this._windowScale * (window.devicePixelRatio || 1) * 2) / 2
this.canvas.width = width * devicePixelRatio * cellSize.width
this.canvas.style.width = `${realWidth}px`
@ -745,8 +747,8 @@ window.TermScreen = class TermScreen {
drawCellBackground ({ x, y, cellWidth, cellHeight, bg }) {
const ctx = this.ctx
ctx.fillStyle = this.getColor(bg)
ctx.clearRect(x * cellWidth, y * cellHeight, Math.ceil(cellWidth), Math.ceil(cellHeight))
ctx.fillRect(x * cellWidth, y * cellHeight, Math.ceil(cellWidth), Math.ceil(cellHeight))
ctx.clearRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight)
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight)
}
/**
@ -1150,8 +1152,6 @@ window.TermScreen = class TermScreen {
this.screenAttrs = new Array(screenLength).fill(' ')
}
let bgcount = new Array(256).fill(0)
let strArray = !undef(Array.from) ? Array.from(str) : str.split('')
const MASK_LINE_ATTR = 0xC8
@ -1173,7 +1173,6 @@ window.TermScreen = class TermScreen {
else this.blinkingCellCount--
}
bgcount[bg]++
this.screen[cell] = lastChar
this.screenFG[cell] = fg
this.screenBG[cell] = bg
@ -1230,18 +1229,6 @@ window.TermScreen = class TermScreen {
if (this.window.debug) console.log(`Blinky cells = ${this.blinkingCellCount}`)
// work-around for the grid gaps bug
// will mask the glitch if most of the screen uses the same background
let mostCommonBg = 0
let mcbIndex = 0
for (let i = 255; i >= 0; i--) {
if (bgcount[i] > mostCommonBg) {
mostCommonBg = bgcount[i]
mcbIndex = i
}
}
this.canvas.style.backgroundColor = this.getColor(mcbIndex)
this.scheduleDraw('load', 16)
this.emit('load')
}

@ -97,7 +97,6 @@ window.TermUpl = function (conn, input, screen) {
inline_pos += MAX_LINE_LEN
}
console.log(chunk)
if (!input.sendString(chunk)) {
updateStatus('FAILED!')
return
@ -134,10 +133,11 @@ window.TermUpl = function (conn, input, screen) {
qs('#fu_file').addEventListener('change', function (evt) {
let reader = new FileReader()
let file = evt.target.files[0]
console.log('Selected file type: ' + file.type)
if (!file.type.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*)/)) {
let ftype = file.type || 'application/octet-stream'
console.log('Selected file type: ' + ftype)
if (!ftype.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*|x-php)/)) {
// 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?')) {
if (!confirm(`This does not look like a text file: ${ftype}\nReally load?`)) {
qs('#fu_file').value = ''
return
}

@ -149,7 +149,7 @@
})
// Forget STA credentials
$('#forget-sta').on('click', function () {
$('#forget-sta').on('click', () => {
selectSta('', '', '')
return false
})

Loading…
Cancel
Save