* Allow for clients such as ConnectBot that see "home" as 0,0 vs ANSI-BBS standard 1,1 by offsetting CPR values
* Some code cleanup * Don't assert in View.js setPosition(); Instead, sanatize values
This commit is contained in:
parent
cf30389146
commit
d50e505bd7
|
@ -31,20 +31,18 @@
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
----/snip/----------------------
|
----/snip/----------------------
|
||||||
*/
|
*/
|
||||||
var term = require('./client_term.js');
|
// ENiGMA½
|
||||||
var miscUtil = require('./misc_util.js');
|
const term = require('./client_term.js');
|
||||||
var ansi = require('./ansi_term.js');
|
const ansi = require('./ansi_term.js');
|
||||||
var Log = require('./logger.js').log;
|
const user = require('./user.js');
|
||||||
var user = require('./user.js');
|
const Config = require('./config.js').config;
|
||||||
var moduleUtil = require('./module_util.js');
|
const MenuStack = require('./menu_stack.js');
|
||||||
var menuUtil = require('./menu_util.js');
|
|
||||||
var Config = require('./config.js').config;
|
|
||||||
var MenuStack = require('./menu_stack.js');
|
|
||||||
const ACS = require('./acs.js');
|
const ACS = require('./acs.js');
|
||||||
|
|
||||||
var stream = require('stream');
|
// deps
|
||||||
var assert = require('assert');
|
const stream = require('stream');
|
||||||
var _ = require('lodash');
|
const assert = require('assert');
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
exports.Client = Client;
|
exports.Client = Client;
|
||||||
|
|
||||||
|
@ -54,28 +52,18 @@ exports.Client = Client;
|
||||||
// Resources & Standards:
|
// Resources & Standards:
|
||||||
// * http://www.ansi-bbs.org/ansi-bbs-core-server.html
|
// * http://www.ansi-bbs.org/ansi-bbs-core-server.html
|
||||||
//
|
//
|
||||||
|
const RE_DSR_RESPONSE_ANYWHERE = /(?:\u001b\[)([0-9\;]+)(R)/;
|
||||||
// :TODO: put this in a common area!!!!...actually, just replace it with more modern code - see ansi_term.js
|
const RE_DEV_ATTR_RESPONSE_ANYWHERE = /(?:\u001b\[)[\=\?]([0-9a-zA-Z\;]+)(c)/;
|
||||||
function getIntArgArray(array) {
|
const RE_META_KEYCODE_ANYWHERE = /(?:\u001b)([a-zA-Z0-9])/;
|
||||||
var i = array.length;
|
const RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$');
|
||||||
while(i--) {
|
const RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:' + [
|
||||||
array[i] = parseInt(array[i], 10);
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
var RE_DSR_RESPONSE_ANYWHERE = /(?:\u001b\[)([0-9\;]+)(R)/;
|
|
||||||
var RE_DEV_ATTR_RESPONSE_ANYWHERE = /(?:\u001b\[)[\=\?]([0-9a-zA-Z\;]+)(c)/;
|
|
||||||
var RE_META_KEYCODE_ANYWHERE = /(?:\u001b)([a-zA-Z0-9])/;
|
|
||||||
var RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$');
|
|
||||||
var RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:' + [
|
|
||||||
'(\\d+)(?:;(\\d+))?([~^$])',
|
'(\\d+)(?:;(\\d+))?([~^$])',
|
||||||
'(?:M([@ #!a`])(.)(.))', // mouse stuff
|
'(?:M([@ #!a`])(.)(.))', // mouse stuff
|
||||||
'(?:1;)?(\\d+)?([a-zA-Z@])'
|
'(?:1;)?(\\d+)?([a-zA-Z@])'
|
||||||
].join('|') + ')');
|
].join('|') + ')');
|
||||||
|
|
||||||
var RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source);
|
const RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source);
|
||||||
var RE_ESC_CODE_ANYWHERE = new RegExp( [
|
const RE_ESC_CODE_ANYWHERE = new RegExp( [
|
||||||
RE_FUNCTION_KEYCODE_ANYWHERE.source,
|
RE_FUNCTION_KEYCODE_ANYWHERE.source,
|
||||||
RE_META_KEYCODE_ANYWHERE.source,
|
RE_META_KEYCODE_ANYWHERE.source,
|
||||||
RE_DSR_RESPONSE_ANYWHERE.source,
|
RE_DSR_RESPONSE_ANYWHERE.source,
|
||||||
|
@ -87,7 +75,8 @@ var RE_ESC_CODE_ANYWHERE = new RegExp( [
|
||||||
function Client(input, output) {
|
function Client(input, output) {
|
||||||
stream.call(this);
|
stream.call(this);
|
||||||
|
|
||||||
var self = this;
|
const self = this;
|
||||||
|
|
||||||
this.user = new user.User();
|
this.user = new user.User();
|
||||||
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
||||||
this.lastKeyPressMs = Date.now();
|
this.lastKeyPressMs = Date.now();
|
||||||
|
@ -118,7 +107,7 @@ function Client(input, output) {
|
||||||
// * Christopher Jeffrey's Blessed library @ https://github.com/chjj/blessed/
|
// * Christopher Jeffrey's Blessed library @ https://github.com/chjj/blessed/
|
||||||
//
|
//
|
||||||
this.getTermClient = function(deviceAttr) {
|
this.getTermClient = function(deviceAttr) {
|
||||||
var termClient = {
|
let termClient = {
|
||||||
//
|
//
|
||||||
// See http://www.fbl.cz/arctel/download/techman.pdf
|
// See http://www.fbl.cz/arctel/download/techman.pdf
|
||||||
//
|
//
|
||||||
|
@ -289,9 +278,13 @@ function Client(input, output) {
|
||||||
var parts;
|
var parts;
|
||||||
|
|
||||||
if((parts = RE_DSR_RESPONSE_ANYWHERE.exec(s))) {
|
if((parts = RE_DSR_RESPONSE_ANYWHERE.exec(s))) {
|
||||||
if('R' === parts[2]) { // :TODO: this should be a assert -- currently only looking for R, unless we start to handle 'n', or others
|
if('R' === parts[2]) {
|
||||||
var cprArgs = getIntArgArray(parts[1].split(';'));
|
const cprArgs = parts[1].split(';').map(v => (parseInt(v, 10) || 0) );
|
||||||
if(2 === cprArgs.length) {
|
if(2 === cprArgs.length) {
|
||||||
|
if(self.cprOffset) {
|
||||||
|
cprArgs[0] = cprArgs[0] + self.cprOffset;
|
||||||
|
cprArgs[1] = cprArgs[1] + self.cprOffset;
|
||||||
|
}
|
||||||
self.emit('cursor position report', cprArgs);
|
self.emit('cursor position report', cprArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
103
core/connect.js
103
core/connect.js
|
@ -4,8 +4,56 @@
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const ansi = require('./ansi_term.js');
|
const ansi = require('./ansi_term.js');
|
||||||
|
|
||||||
|
// deps
|
||||||
|
const async = require('async');
|
||||||
|
|
||||||
exports.connectEntry = connectEntry;
|
exports.connectEntry = connectEntry;
|
||||||
|
|
||||||
|
function ansiDiscoverHomePosition(client, cb) {
|
||||||
|
//
|
||||||
|
// We want to find the home position. ANSI-BBS and most terminals
|
||||||
|
// utilize 1,1 as home. However, some terminals such as ConnectBot
|
||||||
|
// think of home as 0,0. If this is the case, we need to offset
|
||||||
|
// our positioning to accomodate for such.
|
||||||
|
//
|
||||||
|
const done = function(err) {
|
||||||
|
client.removeListener('cursor position report', cprListener);
|
||||||
|
clearTimeout(giveUpTimer);
|
||||||
|
return cb(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cprListener = function(pos) {
|
||||||
|
const h = pos[0];
|
||||||
|
const w = pos[1];
|
||||||
|
|
||||||
|
//
|
||||||
|
// We expect either 0,0, or 1,1. Anything else will be filed as bad data
|
||||||
|
//
|
||||||
|
if(h > 1 || w > 1) {
|
||||||
|
client.log.warn( { height : h, width : w }, 'Ignoring ANSI home position CPR due to unexpected values');
|
||||||
|
return done(new Error('Home position CPR expected to be 0,0, or 1,1'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(0 === h & 0 === w) {
|
||||||
|
//
|
||||||
|
// Store a CPR offset in the client. All CPR's from this point on will offset by this amount
|
||||||
|
//
|
||||||
|
client.log.info('Setting CPR offset to 1');
|
||||||
|
client.cprOffset = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return done(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
client.once('cursor position report', cprListener);
|
||||||
|
|
||||||
|
const giveUpTimer = setTimeout( () => {
|
||||||
|
return done(new Error('Giving up on home position CPR'));
|
||||||
|
}, 3000); // 3s
|
||||||
|
|
||||||
|
client.term.write(`${ansi.goHome()}${ansi.queryPos()}`); // go home, query pos
|
||||||
|
}
|
||||||
|
|
||||||
function ansiQueryTermSizeIfNeeded(client, cb) {
|
function ansiQueryTermSizeIfNeeded(client, cb) {
|
||||||
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
|
@ -25,12 +73,8 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
|
||||||
return done(null);
|
return done(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(2 !== pos.length) {
|
const h = pos[0];
|
||||||
client.log.warn( { cprPosition : pos }, 'Unexpected CPR format');
|
const w = pos[1];
|
||||||
}
|
|
||||||
|
|
||||||
const h = pos[0] || 0;
|
|
||||||
const w = pos[1] || 0;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Netrunner for example gives us 1x1 here. Not really useful. Ignore
|
// Netrunner for example gives us 1x1 here. Not really useful. Ignore
|
||||||
|
@ -76,32 +120,32 @@ function prepareTerminal(term) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayBanner(term) {
|
function displayBanner(term) {
|
||||||
term.pipeWrite(
|
// note: intentional formatting:
|
||||||
'|06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN\n' +
|
term.pipeWrite(`
|
||||||
'|06Copyright (c) 2014-2016 Bryan Ashby |14- |12http://l33t.codes/\n' +
|
|06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN
|
||||||
'|06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/\n' +
|
|06Copyright (c) 2014-2016 Bryan Ashby |14- |12http://l33t.codes/
|
||||||
'|00');
|
|06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/
|
||||||
|
|00`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectEntry(client, nextMenu) {
|
function connectEntry(client, nextMenu) {
|
||||||
const term = client.term;
|
const term = client.term;
|
||||||
|
|
||||||
// :TODO: Enthral for example queries cursor position & checks if it worked. This might be good
|
async.series(
|
||||||
// :TODO: How to detect e.g. if show/hide cursor can work? Probably can if CPR is avail
|
[
|
||||||
|
function basicPrepWork(callback) {
|
||||||
//
|
|
||||||
// Some terminal clients can be detected using a nonstandard ANSI DSR
|
|
||||||
//
|
|
||||||
term.rawWrite(ansi.queryDeviceAttributes(0));
|
term.rawWrite(ansi.queryDeviceAttributes(0));
|
||||||
|
return callback(null);
|
||||||
// :TODO: PuTTY will apparently respond with "PuTTY" if a CTRL-E is sent to it. Add in detection.
|
},
|
||||||
|
function discoverHomePosition(callback) {
|
||||||
//
|
ansiDiscoverHomePosition(client, () => {
|
||||||
// If we don't yet know the client term width/height,
|
// :TODO: If CPR for home fully fails, we should bail out on the connection with an error, e.g. ANSI support required
|
||||||
// try with a nonstandard ANSI DSR type request.
|
return callback(null); // we try to continue anyway
|
||||||
//
|
});
|
||||||
|
},
|
||||||
|
function queryTermSizeByNonStandardAnsi(callback) {
|
||||||
ansiQueryTermSizeIfNeeded(client, err => {
|
ansiQueryTermSizeIfNeeded(client, err => {
|
||||||
|
|
||||||
if(err) {
|
if(err) {
|
||||||
//
|
//
|
||||||
// Check again; We may have got via NAWS/similar before CPR completed.
|
// Check again; We may have got via NAWS/similar before CPR completed.
|
||||||
|
@ -119,6 +163,11 @@ function connectEntry(client, nextMenu) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return callback(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
],
|
||||||
|
() => {
|
||||||
prepareTerminal(term);
|
prepareTerminal(term);
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -129,6 +178,6 @@ function connectEntry(client, nextMenu) {
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
return client.menuStack.goto(nextMenu);
|
return client.menuStack.goto(nextMenu);
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -510,7 +510,6 @@ util.inherits(TelnetClient, baseClient.Client);
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
TelnetClient.prototype.handleTelnetEvent = function(evt) {
|
TelnetClient.prototype.handleTelnetEvent = function(evt) {
|
||||||
// handler name e.g. 'handleWontCommand'
|
// handler name e.g. 'handleWontCommand'
|
||||||
//const handlerName = 'handle' + evt.command.charAt(0).toUpperCase() + evt.command.substr(1) + 'Command';
|
|
||||||
const handlerName = `handle${evt.command.charAt(0).toUpperCase()}${evt.command.substr(1)}Command`;
|
const handlerName = `handle${evt.command.charAt(0).toUpperCase()}${evt.command.substr(1)}Command`;
|
||||||
|
|
||||||
if(this[handlerName]) {
|
if(this[handlerName]) {
|
||||||
|
@ -567,7 +566,7 @@ TelnetClient.prototype.handleDoCommand = function(evt) {
|
||||||
};
|
};
|
||||||
|
|
||||||
TelnetClient.prototype.handleDontCommand = function(evt) {
|
TelnetClient.prototype.handleDontCommand = function(evt) {
|
||||||
this.connectionDebug(evt, 'dont');
|
this.connectionDebug(evt, 'DONT');
|
||||||
};
|
};
|
||||||
|
|
||||||
TelnetClient.prototype.handleSbCommand = function(evt) {
|
TelnetClient.prototype.handleSbCommand = function(evt) {
|
||||||
|
|
17
core/view.js
17
core/view.js
|
@ -123,7 +123,7 @@ View.prototype.setPosition = function(pos) {
|
||||||
if(util.isArray(pos)) {
|
if(util.isArray(pos)) {
|
||||||
this.position.row = pos[0];
|
this.position.row = pos[0];
|
||||||
this.position.col = pos[1];
|
this.position.col = pos[1];
|
||||||
} else if(pos.row && pos.col) {
|
} else if(_.isNumber(pos.row) && _.isNumber(pos.col)) {
|
||||||
this.position.row = pos.row;
|
this.position.row = pos.row;
|
||||||
this.position.col = pos.col;
|
this.position.col = pos.col;
|
||||||
} else if(2 === arguments.length) {
|
} else if(2 === arguments.length) {
|
||||||
|
@ -131,16 +131,11 @@ View.prototype.setPosition = function(pos) {
|
||||||
this.position.col = parseInt(arguments[1], 10);
|
this.position.col = parseInt(arguments[1], 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!(isNaN(this.position.row)));
|
// santaize
|
||||||
assert(!(isNaN(this.position.col)));
|
this.position.row = Math.max(this.position.row, 1);
|
||||||
|
this.position.col = Math.max(this.position.col, 1);
|
||||||
assert(
|
this.position.row = Math.min(this.position.row, this.client.term.termHeight);
|
||||||
this.position.row > 0 && this.position.row <= this.client.term.termHeight,
|
this.position.col = Math.min(this.position.col, this.client.term.termWidth);
|
||||||
'X position ' + this.position.row + ' out of terminal range ' + this.client.term.termHeight);
|
|
||||||
|
|
||||||
assert(
|
|
||||||
this.position.col > 0 && this.position.col <= this.client.term.termWidth,
|
|
||||||
'Y position ' + this.position.col + ' out of terminal range ' + this.client.term.termWidth);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
View.prototype.setDimension = function(dimens) {
|
View.prototype.setDimension = function(dimens) {
|
||||||
|
|
Loading…
Reference in New Issue