|
|
|
'use strict';
|
|
|
|
/*global chrome*/
|
|
|
|
TABS.cli = {
|
|
|
|
lineDelayMs: 50,
|
|
|
|
profileSwitchDelayMs: 100,
|
|
|
|
outputHistory: "",
|
|
|
|
cliBuffer: ""
|
|
|
|
};
|
|
|
|
|
|
|
|
function removePromptHash(promptText) {
|
|
|
|
return promptText.replace(/^# /, '');
|
|
|
|
}
|
|
|
|
|
|
|
|
function cliBufferCharsToDelete(command, buffer) {
|
|
|
|
var commonChars = 0;
|
|
|
|
for (var i = 0;i < buffer.length;i++) {
|
|
|
|
if (command[i] === buffer[i]) {
|
|
|
|
commonChars++;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return buffer.length - commonChars;
|
|
|
|
}
|
|
|
|
|
|
|
|
function commandWithBackSpaces(command, buffer, noOfCharsToDelete) {
|
|
|
|
const backspace = String.fromCharCode(127);
|
|
|
|
return backspace.repeat(noOfCharsToDelete) + command.substring(buffer.length - noOfCharsToDelete, command.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCliCommand(command, cliBuffer) {
|
|
|
|
const buffer = removePromptHash(cliBuffer);
|
|
|
|
const bufferRegex = new RegExp('^' + buffer, 'g');
|
|
|
|
if (command.match(bufferRegex)) {
|
|
|
|
return command.replace(bufferRegex, '');
|
|
|
|
}
|
|
|
|
|
|
|
|
const noOfCharsToDelete = cliBufferCharsToDelete(command, buffer);
|
|
|
|
|
|
|
|
return commandWithBackSpaces(command, buffer, noOfCharsToDelete);
|
|
|
|
}
|
|
|
|
|
|
|
|
TABS.cli.initialize = function (callback) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
if (GUI.active_tab != 'cli') {
|
|
|
|
GUI.active_tab = 'cli';
|
|
|
|
googleAnalytics.sendAppView('CLI');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flush MSP queue as well as all MSP registered callbacks
|
|
|
|
helper.mspQueue.flush();
|
|
|
|
MSP.callbacks_cleanup();
|
|
|
|
|
|
|
|
self.outputHistory = "";
|
|
|
|
self.cliBuffer = "";
|
|
|
|
|
|
|
|
$('#content').load("./tabs/cli.html", function () {
|
|
|
|
// translate to user-selected language
|
|
|
|
localize();
|
|
|
|
|
|
|
|
CONFIGURATOR.cliActive = true;
|
|
|
|
|
|
|
|
var textarea = $('.tab-cli textarea');
|
|
|
|
|
|
|
|
$('.tab-cli .save').click(function() {
|
|
|
|
var prefix = 'cli';
|
|
|
|
var suffix = 'txt';
|
|
|
|
|
|
|
|
var filename = generateFilename(prefix, suffix);
|
|
|
|
|
|
|
|
var accepts = [{
|
|
|
|
description: suffix.toUpperCase() + ' files', extensions: [suffix],
|
|
|
|
}];
|
|
|
|
|
|
|
|
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: filename, accepts: accepts}, function(entry) {
|
|
|
|
if (chrome.runtime.lastError) {
|
|
|
|
if (chrome.runtime.lastError.message === 'User cancelled') {
|
|
|
|
GUI.log(chrome.i18n.getMessage('cliSaveToFileAborted'));
|
|
|
|
} else {
|
|
|
|
GUI.log(chrome.i18n.getMessage('cliSaveToFileFailed'));
|
|
|
|
console.error(chrome.runtime.lastError.message);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!entry) {
|
|
|
|
GUI.log(chrome.i18n.getMessage('cliSaveToFileAborted'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.createWriter(function (writer) {
|
|
|
|
writer.onerror = function (){
|
|
|
|
GUI.log(chrome.i18n.getMessage('cliSaveToFileFailed'));
|
|
|
|
};
|
|
|
|
|
|
|
|
writer.onwriteend = function () {
|
|
|
|
if (self.outputHistory.length > 0 && writer.length === 0) {
|
|
|
|
writer.write(new Blob([self.outputHistory], {type: 'text/plain'}));
|
|
|
|
} else {
|
|
|
|
GUI.log(chrome.i18n.getMessage('cliSaveToFileCompleted'));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
writer.truncate(0);
|
|
|
|
}, function (){
|
|
|
|
GUI.log(chrome.i18n.getMessage('cliSaveToFileFailed'));
|
|
|
|
console.error('Failed to get file writer');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
$('.tab-cli .clear').click(function() {
|
|
|
|
self.outputHistory = "";
|
|
|
|
$('.tab-cli .window .wrapper').empty();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Tab key detection must be on keydown,
|
|
|
|
// `keypress`/`keyup` happens too late, as `textarea` will have already lost focus.
|
|
|
|
textarea.keydown(function (event) {
|
|
|
|
const tabKeyCode = 9;
|
|
|
|
if (event.which == tabKeyCode) {
|
|
|
|
// prevent default tabbing behaviour
|
|
|
|
event.preventDefault();
|
|
|
|
const outString = textarea.val();
|
|
|
|
const lastCommand = outString.split("\n").pop();
|
|
|
|
const command = getCliCommand(lastCommand, self.cliBuffer);
|
|
|
|
if (command) {
|
|
|
|
self.sendAutoComplete(command);
|
|
|
|
textarea.val('');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
textarea.keypress(function (event) {
|
|
|
|
const enterKeyCode = 13;
|
|
|
|
if (event.which == enterKeyCode) {
|
|
|
|
event.preventDefault(); // prevent the adding of new line
|
|
|
|
|
|
|
|
var out_string = textarea.val();
|
|
|
|
self.history.add(out_string.trim());
|
|
|
|
|
|
|
|
var outputArray = out_string.split("\n");
|
|
|
|
Promise.reduce(outputArray, function(delay, line, index) {
|
|
|
|
return new Promise(function (resolve) {
|
|
|
|
helper.timeout.add('CLI_send_slowly', function () {
|
|
|
|
var processingDelay = self.lineDelayMs;
|
|
|
|
if (line.toLowerCase().startsWith('profile')) {
|
|
|
|
processingDelay = self.profileSwitchDelayMs;
|
|
|
|
}
|
|
|
|
const isLastCommand = outputArray.length === index + 1;
|
|
|
|
if (isLastCommand && self.cliBuffer) {
|
|
|
|
line = getCliCommand(line, self.cliBuffer);
|
|
|
|
}
|
|
|
|
self.sendLine(line, function () {
|
|
|
|
resolve(processingDelay);
|
|
|
|
});
|
|
|
|
}, delay)
|
|
|
|
})
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
textarea.val('');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
textarea.keyup(function (event) {
|
|
|
|
var keyUp = {38: true},
|
|
|
|
keyDown = {40: true};
|
|
|
|
|
|
|
|
if (event.keyCode in keyUp) {
|
|
|
|
textarea.val(self.history.prev());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.keyCode in keyDown) {
|
|
|
|
textarea.val(self.history.next());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// give input element user focus
|
|
|
|
textarea.focus();
|
|
|
|
|
|
|
|
helper.timeout.add('enter_cli', function enter_cli() {
|
|
|
|
// Enter CLI mode
|
|
|
|
var bufferOut = new ArrayBuffer(1);
|
|
|
|
var bufView = new Uint8Array(bufferOut);
|
|
|
|
|
|
|
|
bufView[0] = 0x23; // #
|
|
|
|
|
|
|
|
serial.send(bufferOut);
|
|
|
|
}, 250);
|
|
|
|
|
|
|
|
GUI.content_ready(callback);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
TABS.cli.history = {
|
|
|
|
history: [],
|
|
|
|
index: 0
|
|
|
|
};
|
|
|
|
|
|
|
|
TABS.cli.history.add = function (str) {
|
|
|
|
this.history.push(str);
|
|
|
|
this.index = this.history.length;
|
|
|
|
};
|
|
|
|
|
|
|
|
TABS.cli.history.prev = function () {
|
|
|
|
if (this.index > 0) this.index -= 1;
|
|
|
|
return this.history[this.index];
|
|
|
|
};
|
|
|
|
|
|
|
|
TABS.cli.history.next = function () {
|
|
|
|
if (this.index < this.history.length) this.index += 1;
|
|
|
|
return this.history[this.index - 1];
|
|
|
|
};
|
|
|
|
|
|
|
|
const backspaceCode = 8;
|
|
|
|
const lineFeedCode = 10;
|
|
|
|
const carriageReturnCode = 13;
|
|
|
|
|
|
|
|
function writeToOutput(text) {
|
|
|
|
$('.tab-cli .window .wrapper').append(text);
|
|
|
|
$('.tab-cli .window').scrollTop($('.tab-cli .window .wrapper').height());
|
|
|
|
}
|
|
|
|
|
|
|
|
function writeLineToOutput(text) {
|
|
|
|
writeToOutput(text + "<br>");
|
|
|
|
}
|
|
|
|
|
|
|
|
function setPrompt(text) {
|
|
|
|
$('.tab-cli textarea').val(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
TABS.cli.read = function (readInfo) {
|
|
|
|
/* Some info about handling line feeds and carriage return
|
|
|
|
|
|
|
|
line feed = LF = \n = 0x0A = 10
|
|
|
|
carriage return = CR = \r = 0x0D = 13
|
|
|
|
|
|
|
|
MAC only understands CR
|
|
|
|
Linux and Unix only understand LF
|
|
|
|
Windows understands (both) CRLF
|
|
|
|
Chrome OS currently unknown
|
|
|
|
*/
|
|
|
|
var data = new Uint8Array(readInfo.data),
|
|
|
|
validateText = "",
|
|
|
|
sequenceCharsToSkip = 0;
|
|
|
|
|
|
|
|
for (var i = 0; i < data.length; i++) {
|
|
|
|
const currentChar = String.fromCharCode(data[i]);
|
|
|
|
|
|
|
|
if (!CONFIGURATOR.cliValid) {
|
|
|
|
// try to catch part of valid CLI enter message
|
|
|
|
validateText += currentChar;
|
|
|
|
writeToOutput(currentChar);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const escapeSequenceCode = 27;
|
|
|
|
const escapeSequenceCharLength = 3;
|
|
|
|
if (data[i] == escapeSequenceCode && !sequenceCharsToSkip) { // ESC + other
|
|
|
|
sequenceCharsToSkip = escapeSequenceCharLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sequenceCharsToSkip) {
|
|
|
|
sequenceCharsToSkip--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (data[i]) {
|
|
|
|
case lineFeedCode:
|
|
|
|
if (GUI.operating_system === "Windows") {
|
|
|
|
writeLineToOutput(this.cliBuffer);
|
|
|
|
this.cliBuffer = "";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case carriageReturnCode:
|
|
|
|
if (GUI.operating_system !== "Windows") {
|
|
|
|
writeLineToOutput(this.cliBuffer);
|
|
|
|
this.cliBuffer = "";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 60:
|
|
|
|
this.cliBuffer += '<';
|
|
|
|
break;
|
|
|
|
case 62:
|
|
|
|
this.cliBuffer += '>';
|
|
|
|
break;
|
|
|
|
case backspaceCode:
|
|
|
|
this.cliBuffer = this.cliBuffer.slice(0, -1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
this.cliBuffer += currentChar;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.outputHistory += currentChar;
|
|
|
|
|
|
|
|
if (this.cliBuffer == 'Rebooting') {
|
|
|
|
CONFIGURATOR.cliActive = false;
|
|
|
|
CONFIGURATOR.cliValid = false;
|
|
|
|
GUI.log(chrome.i18n.getMessage('cliReboot'));
|
|
|
|
GUI.log(chrome.i18n.getMessage('deviceRebooting'));
|
|
|
|
GUI.handleReconnect();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!CONFIGURATOR.cliValid && validateText.indexOf('CLI') !== -1) {
|
|
|
|
GUI.log(chrome.i18n.getMessage('cliEnter'));
|
|
|
|
CONFIGURATOR.cliValid = true;
|
|
|
|
validateText = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
setPrompt(removePromptHash(this.cliBuffer));
|
|
|
|
};
|
|
|
|
|
|
|
|
TABS.cli.sendLine = function (line, callback) {
|
|
|
|
this.send(line + '\n', callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
TABS.cli.sendAutoComplete = function (line, callback) {
|
|
|
|
this.send(line + '\t', callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
TABS.cli.send = function (line, callback) {
|
|
|
|
var bufferOut = new ArrayBuffer(line.length);
|
|
|
|
var bufView = new Uint8Array(bufferOut);
|
|
|
|
|
|
|
|
for (var c_key = 0; c_key < line.length; c_key++) {
|
|
|
|
bufView[c_key] = line.charCodeAt(c_key);
|
|
|
|
}
|
|
|
|
|
|
|
|
serial.send(bufferOut, callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
TABS.cli.cleanup = function (callback) {
|
|
|
|
if (!(CONFIGURATOR.connectionValid && CONFIGURATOR.cliValid && CONFIGURATOR.cliActive)) {
|
|
|
|
if (callback) callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.send(getCliCommand('exit\r', this.cliBuffer), function (writeInfo) {
|
|
|
|
// we could handle this "nicely", but this will do for now
|
|
|
|
// (another approach is however much more complicated):
|
|
|
|
// we can setup an interval asking for data lets say every 200ms, when data arrives, callback will be triggered and tab switched
|
|
|
|
// we could probably implement this someday
|
|
|
|
helper.timeout.add('waiting_for_bootup', function waiting_for_bootup() {
|
|
|
|
if (callback) callback();
|
|
|
|
}, 1000); // if we dont allow enough time to reboot, CRC of "first" command sent will fail, keep an eye for this one
|
|
|
|
CONFIGURATOR.cliActive = false;
|
|
|
|
});
|
|
|
|
};
|