diff --git a/core/client.js b/core/client.js index 6293779a..d31e8d9f 100644 --- a/core/client.js +++ b/core/client.js @@ -31,20 +31,18 @@ THE SOFTWARE. ----/snip/---------------------- */ -var term = require('./client_term.js'); -var miscUtil = require('./misc_util.js'); -var ansi = require('./ansi_term.js'); -var Log = require('./logger.js').log; -var user = require('./user.js'); -var moduleUtil = require('./module_util.js'); -var menuUtil = require('./menu_util.js'); -var Config = require('./config.js').config; -var MenuStack = require('./menu_stack.js'); +// ENiGMA½ +const term = require('./client_term.js'); +const ansi = require('./ansi_term.js'); +const user = require('./user.js'); +const Config = require('./config.js').config; +const MenuStack = require('./menu_stack.js'); const ACS = require('./acs.js'); -var stream = require('stream'); -var assert = require('assert'); -var _ = require('lodash'); +// deps +const stream = require('stream'); +const assert = require('assert'); +const _ = require('lodash'); exports.Client = Client; @@ -54,28 +52,18 @@ exports.Client = Client; // Resources & Standards: // * http://www.ansi-bbs.org/ansi-bbs-core-server.html // - -// :TODO: put this in a common area!!!!...actually, just replace it with more modern code - see ansi_term.js -function getIntArgArray(array) { - var i = array.length; - while(i--) { - 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|\\[|\\[\\[)(?:' + [ +const RE_DSR_RESPONSE_ANYWHERE = /(?:\u001b\[)([0-9\;]+)(R)/; +const RE_DEV_ATTR_RESPONSE_ANYWHERE = /(?:\u001b\[)[\=\?]([0-9a-zA-Z\;]+)(c)/; +const RE_META_KEYCODE_ANYWHERE = /(?:\u001b)([a-zA-Z0-9])/; +const RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$'); +const RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:' + [ '(\\d+)(?:;(\\d+))?([~^$])', '(?:M([@ #!a`])(.)(.))', // mouse stuff '(?:1;)?(\\d+)?([a-zA-Z@])' ].join('|') + ')'); -var RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source); -var RE_ESC_CODE_ANYWHERE = new RegExp( [ +const RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source); +const RE_ESC_CODE_ANYWHERE = new RegExp( [ RE_FUNCTION_KEYCODE_ANYWHERE.source, RE_META_KEYCODE_ANYWHERE.source, RE_DSR_RESPONSE_ANYWHERE.source, @@ -87,7 +75,8 @@ var RE_ESC_CODE_ANYWHERE = new RegExp( [ function Client(input, output) { stream.call(this); - var self = this; + const self = this; + this.user = new user.User(); this.currentTheme = { info : { name : 'N/A', description : 'None' } }; this.lastKeyPressMs = Date.now(); @@ -118,7 +107,7 @@ function Client(input, output) { // * Christopher Jeffrey's Blessed library @ https://github.com/chjj/blessed/ // this.getTermClient = function(deviceAttr) { - var termClient = { + let termClient = { // // See http://www.fbl.cz/arctel/download/techman.pdf // @@ -289,9 +278,13 @@ function Client(input, output) { var parts; 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 - var cprArgs = getIntArgArray(parts[1].split(';')); + if('R' === parts[2]) { + const cprArgs = parts[1].split(';').map(v => (parseInt(v, 10) || 0) ); 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); } } diff --git a/core/connect.js b/core/connect.js index c9142b6a..b7547190 100644 --- a/core/connect.js +++ b/core/connect.js @@ -4,8 +4,56 @@ // ENiGMA½ const ansi = require('./ansi_term.js'); +// deps +const async = require('async'); + 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) { if(client.term.termHeight > 0 || client.term.termWidth > 0) { return cb(null); @@ -25,12 +73,8 @@ function ansiQueryTermSizeIfNeeded(client, cb) { return done(null); } - if(2 !== pos.length) { - client.log.warn( { cprPosition : pos }, 'Unexpected CPR format'); - } - - const h = pos[0] || 0; - const w = pos[1] || 0; + const h = pos[0]; + const w = pos[1]; // // Netrunner for example gives us 1x1 here. Not really useful. Ignore @@ -76,59 +120,64 @@ function prepareTerminal(term) { } function displayBanner(term) { - term.pipeWrite( - '|06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN\n' + - '|06Copyright (c) 2014-2016 Bryan Ashby |14- |12http://l33t.codes/\n' + - '|06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/\n' + - '|00'); + // note: intentional formatting: + term.pipeWrite(` + |06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN + |06Copyright (c) 2014-2016 Bryan Ashby |14- |12http://l33t.codes/ + |06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/ + |00` + ); } function connectEntry(client, nextMenu) { const term = client.term; - // :TODO: Enthral for example queries cursor position & checks if it worked. This might be good - // :TODO: How to detect e.g. if show/hide cursor can work? Probably can if CPR is avail + async.series( + [ + function basicPrepWork(callback) { + term.rawWrite(ansi.queryDeviceAttributes(0)); + return callback(null); + }, + function discoverHomePosition(callback) { + ansiDiscoverHomePosition(client, () => { + // :TODO: If CPR for home fully fails, we should bail out on the connection with an error, e.g. ANSI support required + return callback(null); // we try to continue anyway + }); + }, + function queryTermSizeByNonStandardAnsi(callback) { + ansiQueryTermSizeIfNeeded(client, err => { + if(err) { + // + // Check again; We may have got via NAWS/similar before CPR completed. + // + if(0 === term.termHeight || 0 === term.termWidth) { + // + // We still don't have something good for term height/width. + // Default to DOS size 80x25. + // + // :TODO: Netrunner is currenting hitting this and it feels wrong. Why is NAWS/ENV/CPR all failing??? + client.log.warn( { reason : err.message }, 'Failed to negotiate term size; Defaulting to 80x25!'); + + term.termHeight = 25; + term.termWidth = 80; + } + } - // - // Some terminal clients can be detected using a nonstandard ANSI DSR - // - term.rawWrite(ansi.queryDeviceAttributes(0)); + return callback(null); + }); + }, + ], + () => { + prepareTerminal(term); - // :TODO: PuTTY will apparently respond with "PuTTY" if a CTRL-E is sent to it. Add in detection. - - // - // If we don't yet know the client term width/height, - // try with a nonstandard ANSI DSR type request. - // - ansiQueryTermSizeIfNeeded(client, err => { - - if(err) { // - // Check again; We may have got via NAWS/similar before CPR completed. + // Always show an ENiGMA½ banner // - if(0 === term.termHeight || 0 === term.termWidth) { - // - // We still don't have something good for term height/width. - // Default to DOS size 80x25. - // - // :TODO: Netrunner is currenting hitting this and it feels wrong. Why is NAWS/ENV/CPR all failing??? - client.log.warn( { reason : err.message }, 'Failed to negotiate term size; Defaulting to 80x25!'); - - term.termHeight = 25; - term.termWidth = 80; - } + displayBanner(term); + + setTimeout( () => { + return client.menuStack.goto(nextMenu); + }, 500); } - - prepareTerminal(term); - - // - // Always show an ENiGMA½ banner - // - displayBanner(term); - - setTimeout( () => { - return client.menuStack.goto(nextMenu); - }, 500); - }); + ); } - diff --git a/core/servers/telnet.js b/core/servers/telnet.js index a211d471..c50aa00c 100644 --- a/core/servers/telnet.js +++ b/core/servers/telnet.js @@ -510,7 +510,6 @@ util.inherits(TelnetClient, baseClient.Client); /////////////////////////////////////////////////////////////////////////////// TelnetClient.prototype.handleTelnetEvent = function(evt) { // 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`; if(this[handlerName]) { @@ -567,7 +566,7 @@ TelnetClient.prototype.handleDoCommand = function(evt) { }; TelnetClient.prototype.handleDontCommand = function(evt) { - this.connectionDebug(evt, 'dont'); + this.connectionDebug(evt, 'DONT'); }; TelnetClient.prototype.handleSbCommand = function(evt) { diff --git a/core/view.js b/core/view.js index 5bf0605a..c8916bf7 100644 --- a/core/view.js +++ b/core/view.js @@ -123,24 +123,19 @@ View.prototype.setPosition = function(pos) { if(util.isArray(pos)) { this.position.row = pos[0]; 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.col = pos.col; } else if(2 === arguments.length) { this.position.row = parseInt(arguments[0], 10); this.position.col = parseInt(arguments[1], 10); } - - assert(!(isNaN(this.position.row))); - assert(!(isNaN(this.position.col))); - assert( - this.position.row > 0 && this.position.row <= this.client.term.termHeight, - '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); + // santaize + this.position.row = Math.max(this.position.row, 1); + this.position.col = Math.max(this.position.col, 1); + this.position.row = Math.min(this.position.row, this.client.term.termHeight); + this.position.col = Math.min(this.position.col, this.client.term.termWidth); }; View.prototype.setDimension = function(dimens) {