/* jslint node: true */ 'use strict'; // ENiGMA½ var Log = require('./logger.js').log; var enigmaToAnsi = require('./color_codes.js').enigmaToAnsi; var renegadeToAnsi = require('./color_codes.js').renegadeToAnsi; var iconv = require('iconv-lite'); var assert = require('assert'); var _ = require('lodash'); exports.ClientTerminal = ClientTerminal; function ClientTerminal(output) { this.output = output; var self = this; var outputEncoding = 'cp437'; assert(iconv.encodingExists(outputEncoding)); // convert line feeds such as \n -> \r\n this.convertLF = true; // // Some terminal we handle specially // They can also be found in this.env{} // var termType = 'unknown'; var termHeight = 0; var termWidth = 0; var termClient = 'unknown'; this.currentSyncFont = 'not_set'; // Raw values set by e.g. telnet NAWS, ENVIRONMENT, etc. this.env = {}; Object.defineProperty(this, 'outputEncoding', { get : function() { return outputEncoding; }, set : function(enc) { if(iconv.encodingExists(enc)) { outputEncoding = enc; } else { Log.warn({ encoding : enc }, 'Unknown encoding'); } } }); Object.defineProperty(this, 'termType', { get : function() { return termType; }, set : function(ttype) { termType = ttype.toLowerCase(); if(this.isANSI()) { this.outputEncoding = 'cp437'; } else { // :TODO: See how x84 does this -- only set if local/remote are binary this.outputEncoding = 'utf8'; } // :TODO: according to this: http://mud-dev.wikidot.com/article:telnet-client-identification // Windows telnet will send "VTNT". If so, set termClient='windows' // there are some others on the page as well Log.debug( { encoding : this.outputEncoding }, 'Set output encoding due to terminal type change'); } }); Object.defineProperty(this, 'termWidth', { get : function() { return termWidth; }, set : function(width) { if(width > 0) { termWidth = width; } } }); Object.defineProperty(this, 'termHeight', { get : function() { return termHeight; }, set : function(height) { if(height > 0) { termHeight = height; } } }); Object.defineProperty(this, 'termClient', { get : function() { return termClient; }, set : function(tc) { termClient = tc; Log.debug( { termClient : this.termClient }, 'Set known terminal client'); } }); } ClientTerminal.prototype.disconnect = function() { this.output = null; }; ClientTerminal.prototype.isNixTerm = function() { // // Standard *nix type terminals // if(this.termType.startsWith('xterm')) { return true; } return [ 'xterm', 'linux', 'screen', 'dumb', 'rxvt', 'konsole', 'gnome', 'x11 terminal emulator' ].includes(this.termType); }; ClientTerminal.prototype.isANSI = function() { // // ANSI terminals should be encoded to CP437 // // Some terminal types provided by Mercyful Fate / Enthral: // ANSI-BBS // PC-ANSI // QANSI // SCOANSI // VT100 // QNX // // Reports from various terminals // // syncterm: // * SyncTERM // // xterm: // * PuTTY // // ansi-bbs: // * fTelnet // // pcansi: // * ZOC // // screen: // * ConnectBot (Android) // // linux: // * JuiceSSH (note: TERM=linux also) // return [ 'ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm' ].includes(this.termType); }; // :TODO: probably need to update these to convert IAC (0xff) -> IACIAC (escape it) ClientTerminal.prototype.write = function(s, convertLineFeeds, cb) { this.rawWrite(this.encode(s, convertLineFeeds), cb); }; ClientTerminal.prototype.rawWrite = function(s, cb) { if(this.output) { this.output.write(s, err => { if(cb) { return cb(err); } if(err) { Log.warn( { error : err.message }, 'Failed writing to socket'); } }); } }; ClientTerminal.prototype.pipeWrite = function(s, spec, cb) { spec = spec || 'renegade'; var conv = { enigma : enigmaToAnsi, renegade : renegadeToAnsi, }[spec] || renegadeToAnsi; this.write(conv(s, this), null, cb); // null = use default for |convertLineFeeds| }; ClientTerminal.prototype.encode = function(s, convertLineFeeds) { convertLineFeeds = _.isBoolean(convertLineFeeds) ? convertLineFeeds : this.convertLF; if(convertLineFeeds && _.isString(s)) { s = s.replace(/\n/g, '\r\n'); } return iconv.encode(s, this.outputEncoding); };