From 5cb239157c9f5b199b9abd1886eae97627e43aaa Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Wed, 23 Mar 2022 18:43:49 -0500 Subject: [PATCH 01/13] First part of removing cursor position reports --- core/ansi_escape_parser.js | 9 +++--- core/art.js | 29 +++++------------ core/client_term.js | 9 ++++-- core/config_default.js | 12 +++++++ core/connect.js | 59 ++++------------------------------- misc/config_template.in.hjson | 9 ++++++ 6 files changed, 45 insertions(+), 82 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 88063bef..ad41fd27 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -190,10 +190,11 @@ function ANSIEscapeParser(options) { self.emit('mci', { - mci : mciCode, - id : id ? parseInt(id, 10) : null, - args : args, - SGR : ansi.getSGRFromGraphicRendition(self.graphicRendition, true) + position : [self.row, self.column], + mci : mciCode, + id : id ? parseInt(id, 10) : null, + args : args, + SGR : ansi.getSGRFromGraphicRendition(self.graphicRendition, true) }); if(self.mciReplaceChar.length > 0) { diff --git a/core/art.js b/core/art.js index 0ff4835f..c8205c47 100644 --- a/core/art.js +++ b/core/art.js @@ -272,16 +272,12 @@ function display(client, art, options, cb) { }); let parseComplete = false; - let cprListener; let mciMap; const mciCprQueue = []; let artHash; let mciMapFromCache; function completed() { - if(cprListener) { - client.removeListener('cursor position report', cprListener); - } if(!options.disableMciCache && !mciMapFromCache) { // cache our MCI findings... @@ -314,18 +310,6 @@ function display(client, art, options, cb) { // no cached MCI info mciMap = {}; - cprListener = function(pos) { - if(mciCprQueue.length > 0) { - mciMap[mciCprQueue.shift()].position = pos; - - if(parseComplete && 0 === mciCprQueue.length) { - return completed(); - } - } - }; - - client.on('cursor position report', cprListener); - let generatedId = 100; ansiParser.on('mci', mciInfo => { @@ -339,18 +323,19 @@ function display(client, art, options, cb) { mapEntry.focusArgs = mciInfo.args; } else { mciMap[mapKey] = { - args : mciInfo.args, - SGR : mciInfo.SGR, - code : mciInfo.mci, - id : id, + position : mciInfo.position, + args : mciInfo.args, + SGR : mciInfo.SGR, + code : mciInfo.mci, + id : id, }; if(!mciInfo.id) { ++generatedId; } - mciCprQueue.push(mapKey); - client.term.rawWrite(ansi.queryPos()); + // mciCprQueue.push(mapKey); + // client.term.rawWrite(ansi.queryPos()); } }); diff --git a/core/client_term.js b/core/client_term.js index d390d8ac..b19b4771 100644 --- a/core/client_term.js +++ b/core/client_term.js @@ -4,11 +4,12 @@ // ENiGMA½ var Log = require('./logger.js').log; var renegadeToAnsi = require('./color_codes.js').renegadeToAnsi; - +const Config = require('./config.js').get; var iconv = require('iconv-lite'); var assert = require('assert'); var _ = require('lodash'); + exports.ClientTerminal = ClientTerminal; function ClientTerminal(output) { @@ -115,7 +116,8 @@ ClientTerminal.prototype.isNixTerm = function() { return true; } - return [ 'xterm', 'linux', 'screen', 'dumb', 'rxvt', 'konsole', 'gnome', 'x11 terminal emulator' ].includes(this.termType); + const utf8TermList = Config().term.utf8TermList; + return utf8TermList.includes(this.termType); }; ClientTerminal.prototype.isANSI = function() { @@ -153,7 +155,8 @@ ClientTerminal.prototype.isANSI = function() { // linux: // * JuiceSSH (note: TERM=linux also) // - return [ 'ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm', 'ansi-256color', 'ansi-256color-rgb' ].includes(this.termType); + const cp437TermList = Config().term.cp437TermList; + return cp437TermList.includes(this.termType); }; // :TODO: probably need to update these to convert IAC (0xff) -> IACIAC (escape it) diff --git a/core/config_default.js b/core/config_default.js index c5e0b86b..d5b71ed7 100644 --- a/core/config_default.js +++ b/core/config_default.js @@ -17,6 +17,18 @@ module.exports = () => { achievementFile : 'achievements.hjson', }, + term : { + // checkUtf8Encoding requires the use of cursor position reports, which are not supported on all terminals. + // Using this with a terminal that does not support cursor position reports results in a 2 second delay + // during the connect process, but provides better autoconfiguration of utf-8 + checkUtf8Encoding : true, + + // List of terms that should be assumed to use cp437 encoding + cp437TermList : ['ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm', 'ansi-256color', 'ansi-256color-rgb'], + // List of terms that should be assumed to use utf8 encoding + utf8TermList : ['xterm', 'linux', 'screen', 'dumb', 'rxvt', 'konsole', 'gnome', 'x11 terminal emulator'], + }, + users : { usernameMin : 2, usernameMax : 16, // Note that FidoNet wants 36 max diff --git a/core/connect.js b/core/connect.js index 29d5443d..d20aae39 100644 --- a/core/connect.js +++ b/core/connect.js @@ -4,6 +4,7 @@ // ENiGMA½ const ansi = require('./ansi_term.js'); const Events = require('./events.js'); +const Config = require('./config.js').get; const { Errors } = require('./enig_error.js'); // deps @@ -11,51 +12,6 @@ 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 accommodate for such. - // - const done = (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(Errors.UnexpectedState('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(Errors.General('Giving up on home position CPR')); - }, 3000); // 3s - - client.term.write(`${ansi.goHome()}${ansi.queryPos()}`); // go home, query pos -} - function ansiAttemptDetectUTF8(client, cb) { // // Trick to attempt and detect UTF-8. While there is a lot more than @@ -68,8 +24,9 @@ function ansiAttemptDetectUTF8(client, cb) { // // We currently only do this if the term hasn't already been ID'd as a // "*nix" terminal -- that is, xterm, etc. - // - if(!client.term.isNixTerm()) { + // Also skip this check if checkUtf8Encoding is disabled in the config + + if(!client.term.isNixTerm() || !Config().term.checkUtf8Encoding) { return cb(null); } @@ -119,6 +76,8 @@ function ansiAttemptDetectUTF8(client, cb) { return giveUp(); }, 2000); + + client.once('cursor position report', cprListener); client.term.rawWrite(ansi.goHome() + ansi.queryPos()); } @@ -214,12 +173,6 @@ function connectEntry(client, nextMenu) { 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) { diff --git a/misc/config_template.in.hjson b/misc/config_template.in.hjson index 21cefeb6..a4dc0c9b 100644 --- a/misc/config_template.in.hjson +++ b/misc/config_template.in.hjson @@ -67,6 +67,15 @@ description : 'Yet another awesome ENiGMA½ BBS' } + term: { + // checkUtf8Encoding requires the use of cursor position reports, which are not supported on all terminals. + // Using this with a terminal that does not support cursor position reports results in a 2 second delay + // during the connect process, but provides better autoconfiguration of utf-8 + checkUtf8Encoding : true + + // other options here include cp437TermList and utf8TermList, see config_default.js for more information + } + paths: { // // Other paths can also be configured as well, From 77e4425df6cb639d7afed1895b75ec7ce0b30d6b Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Thu, 24 Mar 2022 20:01:07 -0500 Subject: [PATCH 02/13] Removed additional cursor position reports. --- core/art.js | 3 --- core/menu_module.js | 36 +++++++++++------------------------- core/theme.js | 14 ++++++++------ 3 files changed, 19 insertions(+), 34 deletions(-) diff --git a/core/art.js b/core/art.js index c8205c47..1d0358c2 100644 --- a/core/art.js +++ b/core/art.js @@ -333,9 +333,6 @@ function display(client, art, options, cb) { if(!mciInfo.id) { ++generatedId; } - - // mciCprQueue.push(mapKey); - // client.term.rawWrite(ansi.queryPos()); } }); diff --git a/core/menu_module.js b/core/menu_module.js index 804065a7..8fd2f5d2 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -56,7 +56,7 @@ exports.MenuModule = class MenuModule extends PluginModule { initSequence() { const self = this; const mciData = {}; - let pausePosition; + let pausePosition = {row: 0, column: 0}; const hasArt = () => { return _.isString(self.menuConfig.art) || @@ -84,19 +84,14 @@ exports.MenuModule = class MenuModule extends PluginModule { self.client.log.trace('Could not display art', { art : self.menuConfig.art, reason : err.message } ); } else { mciData.menu = artData.mciMap; + pausePosition.row = artData.height + 1; } + return callback(null); // any errors are non-fatal } ); }, - function moveToPromptLocation(callback) { - if(self.menuConfig.prompt) { - // :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements - } - - return callback(null); - }, function displayPromptArt(callback) { if(!_.isString(self.menuConfig.prompt)) { return callback(null); @@ -106,30 +101,21 @@ exports.MenuModule = class MenuModule extends PluginModule { return callback(Errors.MissingConfig('Prompt specified but no "promptConfig" block found')); } + self.displayAsset( self.menuConfig.promptConfig.art, self.menuConfig.config, - (err, artData) => { - if(artData) { - mciData.prompt = artData.mciMap; + (err, promptArtData) => { + if(promptArtData) { + mciData.prompt = promptArtData.mciMap; + } + if(promptArtData.height != null) { + pausePosition.row = pausePosition.row + promptArtData.height; } return callback(err); // pass err here; prompts *must* have art } ); }, - function recordCursorPosition(callback) { - if(!self.shouldPause()) { - return callback(null); // cursor position not needed - } - - self.client.once('cursor position report', pos => { - pausePosition = { row : pos[0], col : 1 }; - self.client.log.trace('After art position recorded', pausePosition ); - return callback(null); - }); - - self.client.term.rawWrite(ansi.queryPos()); - }, function afterArtDisplayed(callback) { return self.mciReady(mciData, callback); }, @@ -512,7 +498,7 @@ exports.MenuModule = class MenuModule extends PluginModule { this.optionalMoveToPosition(position); - return theme.displayThemedPause(this.client, cb); + return theme.displayThemedPause(this.client, position, cb); } promptForInput( { formName, formId, promptName, prevFormName, position } = {}, options, cb) { diff --git a/core/theme.js b/core/theme.js index d8340b65..98643c40 100644 --- a/core/theme.js +++ b/core/theme.js @@ -551,6 +551,7 @@ function displayThemedPrompt(name, client, options, cb) { if(options.clearScreen) { client.term.rawWrite(ansi.resetScreen()); + options.position = {row: 1, column: 1}; } // @@ -583,12 +584,11 @@ function displayThemedPrompt(name, client, options, cb) { return callback(null, promptConfig, artInfo); } - client.once('cursor position report', pos => { - artInfo.startRow = pos[0] - artInfo.height; - return callback(null, promptConfig, artInfo); - }); + if(options.row != null) { + artInfo.startRow = options.row - artInfo.height; + } - client.term.rawWrite(ansi.queryPos()); + return callback(null, promptConfig, artInfo); }, function createMCIViews(promptConfig, artInfo, callback) { const assocViewController = usingTempViewController ? new ViewController( { client : client } ) : options.viewController; @@ -614,7 +614,9 @@ function displayThemedPrompt(name, client, options, cb) { }); }, function clearPauseArt(artInfo, assocViewController, callback) { - if(options.clearPrompt) { + // Only clear with height if clearPrompt is true and if we were able + // to determine the row + if(options.clearPrompt && artInfo.startRow) { if(artInfo.startRow && artInfo.height) { client.term.rawWrite(ansi.goto(artInfo.startRow, 1)); From b7da0dc82a3db1633a6ed2b8c1c51945b5830108 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Fri, 25 Mar 2022 19:27:01 -0500 Subject: [PATCH 03/13] Getting closer, fixed fse --- core/ansi_escape_parser.js | 8 +++- core/art.js | 1 + core/fse.js | 87 ++++++++++++++++++++++++-------------- core/menu_module.js | 5 +++ core/show_art.js | 19 +++------ core/theme.js | 9 +++- 6 files changed, 81 insertions(+), 48 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index ad41fd27..c6d1276c 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -21,10 +21,16 @@ function ANSIEscapeParser(options) { events.EventEmitter.call(this); this.column = 1; - this.row = 1; this.scrollBack = 0; this.graphicRendition = {}; + if(options.startRow != null) { + this.row = options.startRow; + } + else { + this.row = 1; + } + this.parseState = { re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex }; diff --git a/core/art.js b/core/art.js index 1d0358c2..7385d08a 100644 --- a/core/art.js +++ b/core/art.js @@ -269,6 +269,7 @@ function display(client, art, options, cb) { termHeight : client.term.termHeight, termWidth : client.term.termWidth, trailingLF : options.trailingLF, + startRow : options.startRow, }); let parseComplete = false; diff --git a/core/fse.js b/core/fse.js index 361bbeff..8674fb3b 100644 --- a/core/fse.js +++ b/core/fse.js @@ -603,7 +603,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul theme.displayThemedAsset( footerArt, self.client, - { font : self.menuConfig.font }, + { font : self.menuConfig.font, startRow: self.header.height + self.body.height }, function displayed(err, artData) { callback(err, artData); } @@ -626,19 +626,34 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul async.series( [ function displayHeaderAndBody(callback) { - async.eachSeries( comps, function dispArt(n, next) { - theme.displayThemedAsset( - art[n], - self.client, - { font : self.menuConfig.font }, - function displayed(err) { - next(err); + async.waterfall( + [ + function displayHeader(callback) { + theme.displayThemedAsset( + art['header'], + self.client, + { font : self.menuConfig.font }, + function displayed(err, artInfo) { + return callback(err, artInfo); + } + ); + }, + function displayBody(artInfo, callback) { + theme.displayThemedAsset( + art['header'], + self.client, + { font : self.menuConfig.font, startRow: artInfo.height + 1 }, + function displayed(err, artInfo) { + return callback(err, artInfo); + } + ); } - ); - }, function complete(err) { - //self.body.height = self.client.term.termHeight - self.header.height - 1; - callback(err); - }); + ], + function complete(err) { + //self.body.height = self.client.term.termHeight - self.header.height - 1; + callback(err); + } + ); }, function displayFooter(callback) { // we have to treat the footer special @@ -700,31 +715,39 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul assert(_.isObject(art)); - async.series( + async.waterfall( [ function beforeDisplayArt(callback) { self.beforeArt(callback); }, - function displayHeaderAndBodyArt(callback) { - async.eachSeries( [ 'header', 'body' ], function dispArt(n, next) { - theme.displayThemedAsset( - art[n], - self.client, - { font : self.menuConfig.font }, - function displayed(err, artData) { - if(artData) { - mciData[n] = artData; - self[n] = { height : artData.height }; - } - - next(err); + function displayHeader(callback) { + theme.displayThemedAsset( + art.header, + self.client, + { font : self.menuConfig.font }, + function displayed(err, artInfo) { + if(artInfo) { + mciData['header'] = artInfo; + self.header = {height: artInfo.height}; } - ); - }, function complete(err) { - callback(err); - }); + return callback(err, artInfo); + } + ); }, - function displayFooter(callback) { + function displayBody(artInfo, callback) { + theme.displayThemedAsset( + art.body, + self.client, + { font : self.menuConfig.font, startRow: artInfo.height }, + function displayed(err, artInfo) { + if(artInfo) { + mciData['body'] = artInfo; + self.body = {height: artInfo.height - self.header.height}; + } + return callback(err, artInfo); + }); + }, + function displayFooter(artInfo, callback) { self.setInitialFooterMode(); var footerName = self.getFooterName(); diff --git a/core/menu_module.js b/core/menu_module.js index 8fd2f5d2..990f7f78 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -124,6 +124,11 @@ exports.MenuModule = class MenuModule extends PluginModule { return callback(null); } + if(self.client.term.termHeight > 0 && pausePosition.row > self.client.termHeight) { + // If this scrolled, the prompt will go to the bottom of the screen + pausePosition.row = self.client.termHeight; + } + return self.pausePrompt(pausePosition, callback); }, function finishAndNext(callback) { diff --git a/core/show_art.js b/core/show_art.js index 7e53ca60..4236760c 100644 --- a/core/show_art.js +++ b/core/show_art.js @@ -171,22 +171,15 @@ exports.getModule = class ShowArtModule extends MenuModule { return callback(err); } const mciData = { menu : artData.mciMap }; - return callback(null, mciData); + if(self.client.term.termHeight > 0 && artData.height > self.client.term.termHeight) { + // We must have scrolled, adjust the positioning for pause + artData.height = self.client.term.termHeight; + } + const pausePosition = { row: artData.height + 1, col: 1}; + return callback(null, mciData, pausePosition); } ); }, - function recordCursorPosition(mciData, callback) { - if(!options.pause) { - return callback(null, mciData, null); // cursor position not needed - } - - self.client.once('cursor position report', pos => { - const pausePosition = { row : pos[0], col : 1 }; - return callback(null, mciData, pausePosition); - }); - - self.client.term.rawWrite(ANSI.queryPos()); - }, function afterArtDisplayed(mciData, pausePosition, callback) { self.mciReady(mciData, err => { return callback(err, pausePosition); diff --git a/core/theme.js b/core/theme.js index 98643c40..f574c62f 100644 --- a/core/theme.js +++ b/core/theme.js @@ -495,6 +495,7 @@ function displayPreparedArt(options, artInfo, cb) { sauce : artInfo.sauce, font : options.font, trailingLF : options.trailingLF, + startRow : options.startRow, }; art.display(options.client, artInfo.data, displayOpts, (err, mciMap, extraInfo) => { return cb(err, { mciMap : mciMap, artInfo : artInfo, extraInfo : extraInfo } ); @@ -585,7 +586,11 @@ function displayThemedPrompt(name, client, options, cb) { } if(options.row != null) { - artInfo.startRow = options.row - artInfo.height; + artInfo.startRow = options.row; + if(client.term.termHeight > 0 && artInfo.startRow + artInfo.height > client.term.termHeight) { + // in this case, we will have scrolled + artInfo.startRow = client.term.termHeight - artInfo.height; + } } return callback(null, promptConfig, artInfo); @@ -614,7 +619,7 @@ function displayThemedPrompt(name, client, options, cb) { }); }, function clearPauseArt(artInfo, assocViewController, callback) { - // Only clear with height if clearPrompt is true and if we were able + // Only clear with height if clearPrompt is true and if we were able // to determine the row if(options.clearPrompt && artInfo.startRow) { if(artInfo.startRow && artInfo.height) { From f72b4659b39f26e31e525e74690f3aeee585eb6d Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Fri, 25 Mar 2022 19:31:39 -0500 Subject: [PATCH 04/13] Small fix on fse formatting --- core/fse.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/fse.js b/core/fse.js index 8674fb3b..a0d8750b 100644 --- a/core/fse.js +++ b/core/fse.js @@ -738,7 +738,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul theme.displayThemedAsset( art.body, self.client, - { font : self.menuConfig.font, startRow: artInfo.height }, + { font : self.menuConfig.font, startRow: artInfo.height + 1 }, function displayed(err, artInfo) { if(artInfo) { mciData['body'] = artInfo; @@ -848,8 +848,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul { const fromView = self.viewControllers.header.getView(MciViewIds.header.from); const area = getMessageAreaByTag(self.messageAreaTag); - - if(fromView !== undefined) { + if(fromView !== undefined) { if(area && area.realNames) { fromView.setText(self.client.user.properties[UserProps.RealName] || self.client.user.username); } else { From 4aed3c059dff0ed697e5af25d2660929110f7a68 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Fri, 25 Mar 2022 20:10:33 -0500 Subject: [PATCH 05/13] Fixed prompt location. --- core/menu_module.js | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/core/menu_module.js b/core/menu_module.js index 990f7f78..ef6ad831 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -63,7 +63,7 @@ exports.MenuModule = class MenuModule extends PluginModule { (Array.isArray(self.menuConfig.art) && _.has(self.menuConfig.art[0], 'acs')); }; - async.series( + async.waterfall( [ function beforeArtInterrupt(callback) { return self.displayQueuedInterruptions(callback); @@ -73,7 +73,7 @@ exports.MenuModule = class MenuModule extends PluginModule { }, function displayMenuArt(callback) { if(!hasArt()) { - return callback(null); + return callback(null, null); } self.displayAsset( @@ -84,15 +84,17 @@ exports.MenuModule = class MenuModule extends PluginModule { self.client.log.trace('Could not display art', { art : self.menuConfig.art, reason : err.message } ); } else { mciData.menu = artData.mciMap; + } + + if(artData) { pausePosition.row = artData.height + 1; } - - return callback(null); // any errors are non-fatal + return callback(null, artData); // any errors are non-fatal } ); }, - function displayPromptArt(callback) { + function displayPromptArt(artData, callback) { if(!_.isString(self.menuConfig.prompt)) { return callback(null); } @@ -101,17 +103,21 @@ exports.MenuModule = class MenuModule extends PluginModule { return callback(Errors.MissingConfig('Prompt specified but no "promptConfig" block found')); } + const options = self.menuConfig.config; + + if(artData != null && artData.height != null) { + options.startRow = artData.height + 1; + } self.displayAsset( self.menuConfig.promptConfig.art, - self.menuConfig.config, - (err, promptArtData) => { - if(promptArtData) { - mciData.prompt = promptArtData.mciMap; - } - if(promptArtData.height != null) { - pausePosition.row = pausePosition.row + promptArtData.height; + options, + (err, artData) => { + if(artData) { + mciData.prompt = artData.mciMap; + pausePosition.row = artData.height + 1; } + return callback(err); // pass err here; prompts *must* have art } ); @@ -131,7 +137,7 @@ exports.MenuModule = class MenuModule extends PluginModule { return self.pausePrompt(pausePosition, callback); }, - function finishAndNext(callback) { + function finishAndNext(artInfo, callback) { self.finishedLoading(); self.realTimeInterrupt = 'allowed'; return self.autoNextMenu(callback); From abaca6c8ef584e4f252d908831fc020d53b60428 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Mon, 28 Mar 2022 14:18:00 -0500 Subject: [PATCH 06/13] Fixed changes from code review --- core/ansi_escape_parser.js | 2 +- core/config_default.js | 6 ++++ core/connect.js | 57 ++++++++++++++++++++++++++++++++++++++ core/menu_module.js | 4 +-- 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index c6d1276c..73c4f377 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -24,7 +24,7 @@ function ANSIEscapeParser(options) { this.scrollBack = 0; this.graphicRendition = {}; - if(options.startRow != null) { + if(!_.isNil(options.startRow)) { this.row = options.startRow; } else { diff --git a/core/config_default.js b/core/config_default.js index d5b71ed7..c51fb6a1 100644 --- a/core/config_default.js +++ b/core/config_default.js @@ -23,6 +23,12 @@ module.exports = () => { // during the connect process, but provides better autoconfiguration of utf-8 checkUtf8Encoding : true, + // Checking the ANSI home position also requires the use of cursor position reports, which are not + // supported on all terminals. Using this with a terminal that does not support cursor position reports + // results in a 3 second delay during the connect process, but works around positioning problems with + // non-standard terminals. + checkAnsiHomePosition: true, + // List of terms that should be assumed to use cp437 encoding cp437TermList : ['ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm', 'ansi-256color', 'ansi-256color-rgb'], // List of terms that should be assumed to use utf8 encoding diff --git a/core/connect.js b/core/connect.js index d20aae39..9acf002f 100644 --- a/core/connect.js +++ b/core/connect.js @@ -12,6 +12,58 @@ 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 accommodate for such. + // + + + if( !Config().term.checkAnsiHomePosition ) { + // Skip (and assume 1,1) if the home position check is disabled. + return cb(null); + } + + const done = (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(Errors.UnexpectedState('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(Errors.General('Giving up on home position CPR')); + }, 3000); // 3s + + client.term.write(`${ansi.goHome()}${ansi.queryPos()}`); // go home, query pos +} + function ansiAttemptDetectUTF8(client, cb) { // // Trick to attempt and detect UTF-8. While there is a lot more than @@ -173,6 +225,11 @@ function connectEntry(client, nextMenu) { term.rawWrite(ansi.queryDeviceAttributes(0)); return callback(null); }, + function discoverHomePosition(callback) { + ansiDiscoverHomePosition(client, () => { + return callback(null); // we try to continue anyway + }); + }, function queryTermSizeByNonStandardAnsi(callback) { ansiQueryTermSizeIfNeeded(client, err => { if(err) { diff --git a/core/menu_module.js b/core/menu_module.js index ef6ad831..54bda2c7 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -105,7 +105,7 @@ exports.MenuModule = class MenuModule extends PluginModule { const options = self.menuConfig.config; - if(artData != null && artData.height != null) { + if(!_.isNil(artData) && _.isNumber(artData.height)) { options.startRow = artData.height + 1; } @@ -127,7 +127,7 @@ exports.MenuModule = class MenuModule extends PluginModule { }, function displayPauseIfRequested(callback) { if(!self.shouldPause()) { - return callback(null); + return callback(null, null); } if(self.client.term.termHeight > 0 && pausePosition.row > self.client.termHeight) { From ec2f6af1d00b9ec7dcfc4e4478c488f2c06ac2ba Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Mon, 28 Mar 2022 14:41:12 -0500 Subject: [PATCH 07/13] More code review fixes --- core/menu_module.js | 2 +- core/theme.js | 4 ++-- misc/config_template.in.hjson | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/menu_module.js b/core/menu_module.js index 54bda2c7..94331e6d 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -509,7 +509,7 @@ exports.MenuModule = class MenuModule extends PluginModule { this.optionalMoveToPosition(position); - return theme.displayThemedPause(this.client, position, cb); + return theme.displayThemedPause(this.client, {position}, cb); } promptForInput( { formName, formId, promptName, prevFormName, position } = {}, options, cb) { diff --git a/core/theme.js b/core/theme.js index f574c62f..52ce243c 100644 --- a/core/theme.js +++ b/core/theme.js @@ -585,8 +585,8 @@ function displayThemedPrompt(name, client, options, cb) { return callback(null, promptConfig, artInfo); } - if(options.row != null) { - artInfo.startRow = options.row; + if(!_.isNil(options) && !_.isNil(options.position) && !_.isNil(options.position.row)) { + artInfo.startRow = options.position.row; if(client.term.termHeight > 0 && artInfo.startRow + artInfo.height > client.term.termHeight) { // in this case, we will have scrolled artInfo.startRow = client.term.termHeight - artInfo.height; diff --git a/misc/config_template.in.hjson b/misc/config_template.in.hjson index a4dc0c9b..5b51b43a 100644 --- a/misc/config_template.in.hjson +++ b/misc/config_template.in.hjson @@ -73,6 +73,12 @@ // during the connect process, but provides better autoconfiguration of utf-8 checkUtf8Encoding : true + // Checking the ANSI home position also requires the use of cursor position reports, which are not + // supported on all terminals. Using this with a terminal that does not support cursor position reports + // results in a 3 second delay during the connect process, but works around positioning problems with + // non-standard terminals. + checkAnsiHomePosition: true + // other options here include cp437TermList and utf8TermList, see config_default.js for more information } From 590f494f44b7f992d0c856dd841e28f0fb86cec8 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Mon, 28 Mar 2022 17:52:52 -0500 Subject: [PATCH 08/13] Upgraded node.js and bumped version --- .eslintrc.json | 3 +++ core/ansi_escape_parser.js | 2 +- core/menu_module.js | 2 +- core/theme.js | 2 +- package.json | 6 +++--- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 53bd1287..fbe0b672 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -28,5 +28,8 @@ ], "comma-dangle": 0, "no-trailing-spaces" :"warn" + }, + "parserOptions": { + "ecmaVersion": 2020 } } \ No newline at end of file diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 73c4f377..c8101267 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -24,7 +24,7 @@ function ANSIEscapeParser(options) { this.scrollBack = 0; this.graphicRendition = {}; - if(!_.isNil(options.startRow)) { + if(!_.isNil(options?.startRow)) { this.row = options.startRow; } else { diff --git a/core/menu_module.js b/core/menu_module.js index 94331e6d..7669c2ee 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -105,7 +105,7 @@ exports.MenuModule = class MenuModule extends PluginModule { const options = self.menuConfig.config; - if(!_.isNil(artData) && _.isNumber(artData.height)) { + if(_.isNumber(artData?.height)) { options.startRow = artData.height + 1; } diff --git a/core/theme.js b/core/theme.js index 52ce243c..7b6d7940 100644 --- a/core/theme.js +++ b/core/theme.js @@ -585,7 +585,7 @@ function displayThemedPrompt(name, client, options, cb) { return callback(null, promptConfig, artInfo); } - if(!_.isNil(options) && !_.isNil(options.position) && !_.isNil(options.position.row)) { + if(!_.isNil(options?.position?.row)) { artInfo.startRow = options.position.row; if(client.term.termHeight > 0 && artInfo.startRow + artInfo.height > client.term.termHeight) { // in this case, we will have scrolled diff --git a/package.json b/package.json index c828edc4..b577acce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "enigma-bbs", - "version": "0.0.12-beta", + "version": "0.0.14-beta", "description": "ENiGMA½ Bulletin Board System", "author": "Bryan Ashby ", "license": "BSD-2-Clause", @@ -62,9 +62,9 @@ "yazl": "^2.5.1" }, "devDependencies": { - "eslint": "7.19.0" + "eslint": "8.12.0" }, "engines": { - "node": ">=12" + "node": ">=14" } } From 40a4e306de4bef558420d3434a9477665bda5a34 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Mon, 28 Mar 2022 17:59:21 -0500 Subject: [PATCH 09/13] Updated documentation for Node v14 --- docs/_docs/installation/manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_docs/installation/manual.md b/docs/_docs/installation/manual.md index 615dec67..eb8101aa 100644 --- a/docs/_docs/installation/manual.md +++ b/docs/_docs/installation/manual.md @@ -6,7 +6,7 @@ For Linux environments it's recommended you run the [install script](install-scr do things manually, read on... ## Prerequisites -* [Node.js](https://nodejs.org/) version **v12.x LTS or higher** (Other versions may work but are not supported). +* [Node.js](https://nodejs.org/) version **v14.x LTS or higher**. Versions under v14 are known not to work due to language level changes. * :bulb: It is **highly** recommended to use [Node Version Manager (NVM)](https://github.com/creationix/nvm) to manage your Node.js installation if you're on a Linux/Unix environment. * [Python](https://www.python.org/downloads/) for compiling Node.js packages with native extensions via `node-gyp`. From 44129f3348b002c1bdf44ab74eb9fb2a2c2f9971 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Mon, 28 Mar 2022 18:51:43 -0500 Subject: [PATCH 10/13] Updated version and diff changes --- core/ansi_escape_parser.js | 19 ++++++------------- package.json | 2 +- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index c8101267..5417cef5 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -21,16 +21,8 @@ function ANSIEscapeParser(options) { events.EventEmitter.call(this); this.column = 1; - this.scrollBack = 0; this.graphicRendition = {}; - if(!_.isNil(options?.startRow)) { - this.row = options.startRow; - } - else { - this.row = 1; - } - this.parseState = { re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex }; @@ -42,11 +34,15 @@ function ANSIEscapeParser(options) { trailingLF : 'default', // default|omit|no|yes, ... }); + this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, ''); this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25); this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80); this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default'); + + this.row = Math.min(options?.startRow ?? 1, this.termHeight); + self.moveCursor = function(cols, rows) { self.column += cols; self.row += rows; @@ -75,14 +71,11 @@ function ANSIEscapeParser(options) { }; self.clearScreen = function() { - // :TODO: should be doing something with row/column? + self.column = 1; + self.row = 1; self.emit('clear screen'); }; - /* - self.rowUpdated = function() { - self.emit('row update', self.row + self.scrollBack); - };*/ self.positionUpdated = function() { self.emit('position update', self.row, self.column); diff --git a/package.json b/package.json index b577acce..2e45f18e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "enigma-bbs", - "version": "0.0.14-beta", + "version": "0.0.13-beta", "description": "ENiGMA½ Bulletin Board System", "author": "Bryan Ashby ", "license": "BSD-2-Clause", From 94e4f1240e015afb8add669c4306cfb1eec84764 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Mon, 28 Mar 2022 19:38:14 -0500 Subject: [PATCH 11/13] Small code review change --- core/theme.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/theme.js b/core/theme.js index 7b6d7940..a511ba33 100644 --- a/core/theme.js +++ b/core/theme.js @@ -585,7 +585,7 @@ function displayThemedPrompt(name, client, options, cb) { return callback(null, promptConfig, artInfo); } - if(!_.isNil(options?.position?.row)) { + if(_.isNumber(options?.position?.row)) { artInfo.startRow = options.position.row; if(client.term.termHeight > 0 && artInfo.startRow + artInfo.height > client.term.termHeight) { // in this case, we will have scrolled From e9d22b0688bef53880bffafab4e9a3fd27e11a7b Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Mon, 28 Mar 2022 22:20:55 -0500 Subject: [PATCH 12/13] Additional code changes from PR --- core/ansi_escape_parser.js | 3 +++ core/menu_module.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 5417cef5..438f6203 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -215,6 +215,9 @@ function ANSIEscapeParser(options) { } self.reset = function(input) { + self.column = 1; + self.row = Math.min(options?.startRow ?? 1, self.termHeight); + self.parseState = { // ignore anything past EOF marker, if any buffer : input.split(String.fromCharCode(0x1a), 1)[0], diff --git a/core/menu_module.js b/core/menu_module.js index 7669c2ee..bfddbd18 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -103,7 +103,7 @@ exports.MenuModule = class MenuModule extends PluginModule { return callback(Errors.MissingConfig('Prompt specified but no "promptConfig" block found')); } - const options = self.menuConfig.config; + const options = Object.assign({}, self.menuConfig.config); if(_.isNumber(artData?.height)) { options.startRow = artData.height + 1; From 8078abb3e46edf2e11e553f1855c3a27a2f63331 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Tue, 5 Apr 2022 17:25:50 -0500 Subject: [PATCH 13/13] Updated for new release --- UPGRADE.md | 27 +++++++++++++++++++++++++++ WHATSNEW.md | 8 +++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/UPGRADE.md b/UPGRADE.md index f87c2079..f758d9d2 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -28,6 +28,33 @@ npm install # or simply 'yarn' Report your issue on Xibalba BBS, hop in #enigma-bbs on FreeNode and chat, or [file a issue on GitHub](https://github.com/NuSkooler/enigma-bbs/issues). + +# 0.0.12-beta to 0.0.13-beta + +* All features and changes are backwards compatible. There are a few new configuration options in a new `term` section in the configuration. These are all optional, but include the following options in case you use them: + +```hjson +{ + + term: { + // checkUtf8Encoding requires the use of cursor position reports, which are not supported on all terminals. + // Using this with a terminal that does not support cursor position reports results in a 2 second delay + // during the connect process, but provides better autoconfiguration of utf-8 + checkUtf8Encoding: true + + + // Checking the ANSI home position also requires the use of cursor position reports, which are not + // supported on all terminals. Using this with a terminal that does not support cursor position reports + // results in a 3 second delay during the connect process, but works around positioning problems with + // non-standard terminals. + checkAnsiHomePosition: true + } +} + +``` + +In addition to these, there are also new options for `term.cp437TermList` and `term.utf8TermList`. Under most circumstances these should not need to be changed. If you want to customize these lists, more information is available in `config_default.js` + # 0.0.11-beta to 0.0.12-beta * Be aware that `master` is now mainline! This means all `git pull`'s will yield the latest version. See [WHATSNEW](WHATSNEW.md) for more information. * **BREAKING CHANGE** There is no longer a `prompt.hjson` file. Prompts are now simply part of the menu set in the `prompts` section. If you have an existing system you will need to add your `prompt.hjson` to your `menu.hjson`'s `includes` section at a minimum. Example: diff --git a/WHATSNEW.md b/WHATSNEW.md index 89ee5a7b..5602f258 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -1,6 +1,12 @@ # Whats New This document attempts to track **major** changes and additions in ENiGMA½. For details, see GitHub. +## 0.0.13-beta +* Removed terminal `cursor position reports` from most locations in the code. This should greatly increase the number of terminal programs that work with Enigma 1/2. For more information, see [Issue #222](https://github.com/NuSkooler/enigma-bbs/issues/222). This may also resolve other issues, such as [Issue #365](https://github.com/NuSkooler/enigma-bbs/issues/365), and [Issue #320](https://github.com/NuSkooler/enigma-bbs/issues/320). Anyone that previously had terminal incompatibilities please re-check and let us know! +* Bumped up the minimum [Node.js](https://nodejs.org/en/) version to V14. This will allow more expressive Javascript programming syntax with ECMAScript 2020 to improve the development experience. +* Added new configuration options for `term.checkUtf8Encoding`, `term.checkAnsiHomePostion`, `term.cp437TermList`, and `term.utf8TermList`. More information on these options is available in `UPGRADE.md` +* Many additional backward-compatible bug fixes since the first release of 0.0.12-beta. See the [project repository](https://github.com/NuSkooler/enigma-bbs) for more information. + ## 0.0.12-beta * The `master` branch has become mainline. What this means to users is `git pull` will always give you the latest and greatest. Make sure to read [Updating](./docs/admin/updating.md) and keep an eye on `WHATSNEW.md` (this file) and [UPGRADE](UPGRADE.md)! See also [ticket #276](https://github.com/NuSkooler/enigma-bbs/issues/276). * Development now occurs against [Node.js 14 LTS](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V14.md). @@ -127,4 +133,4 @@ submit: [ ...LOTS more! ## Pre 0.0.8-alpha -See GitHub \ No newline at end of file +See GitHub