From 40e0b5542482dacc6f3b304969a867219d0601be Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Mon, 6 Jul 2015 22:37:11 -0600 Subject: [PATCH] * Try to handle socket error * Fix bug in wrapping with MultiLineEditText * Updates on message base DB layout/triggers * Detect some terminals via ANSI DSR for device attributes (WIP) --- core/ansi_term.js | 2 ++ core/client.js | 40 ++++++++++++++++++++++++++++--- core/client_term.js | 18 ++++++++++++++ core/connect.js | 7 ++++++ core/database.js | 8 +++---- core/multi_line_edit_text_view.js | 14 +++++++---- core/servers/telnet.js | 5 ++++ 7 files changed, 83 insertions(+), 11 deletions(-) diff --git a/core/ansi_term.js b/core/ansi_term.js index a4409245..15766a4e 100644 --- a/core/ansi_term.js +++ b/core/ansi_term.js @@ -73,6 +73,8 @@ var CONTROL = { hideCursor : '?25l', // Nonstandard - cterm.txt showCursor : '?25h', // Nonstandard - cterm.txt + queryDeviceAttributes : 'c', // Nonstandard - cterm.txt + // :TODO: see https://code.google.com/p/conemu-maximus5/wiki/AnsiEscapeCodes // apparently some terms can report screen size and text area via 18t and 19t }; diff --git a/core/client.js b/core/client.js index 3173be5b..02c52686 100644 --- a/core/client.js +++ b/core/client.js @@ -60,7 +60,8 @@ function getIntArgArray(array) { return array; } -var RE_DSR_RESPONSE_ANYWHERE = /(?:\u001b\[)([0-9\;]+)([R])/; +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|\\[|\\[\\[)(?:' + [ @@ -74,6 +75,7 @@ var RE_ESC_CODE_ANYWHERE = new RegExp( [ RE_FUNCTION_KEYCODE_ANYWHERE.source, RE_META_KEYCODE_ANYWHERE.source, RE_DSR_RESPONSE_ANYWHERE.source, + RE_DEV_ATTR_RESPONSE_ANYWHERE.source, /\u001b./.source ].join('|')); @@ -98,7 +100,7 @@ function Client(input, output) { this.output = output; this.term = new term.ClientTerminal(this.output); this.user = new user.User(); - this.currentTheme = { info : { name : 'N/A', description : 'None' } }; + this.currentTheme = { info : { name : 'N/A', description : 'None' } }; // // Peek at incoming |data| and emit events for any special @@ -110,6 +112,32 @@ function Client(input, output) { // * http://www.ansi-bbs.org/ansi-bbs-core-server.html // * Christopher Jeffrey's Blessed library @ https://github.com/chjj/blessed/ // + this.getTermClient = function(deviceAttr) { + var termClient = { + // + // See http://www.fbl.cz/arctel/download/techman.pdf + // + // Known clients: + // * Irssi ConnectBot (Android) + // + '63;1;2' : 'arctel', + }[deviceAttr]; + + if(!termClient) { + if(_.startsWith(deviceAttr, '67;84;101;114;109')) { + // + // See https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt + // + // Known clients: + // * SyncTERM + // + termClient = 'cterm'; + } + } + + return termClient; + }; + this.isMouseInput = function(data) { return /\x1b\[M/.test(data) || /\u001b\[M([\x00\u0020-\uffff]{3})/.test(data) || @@ -256,12 +284,18 @@ function Client(input, output) { var parts; if((parts = RE_DSR_RESPONSE_ANYWHERE.exec(s))) { - if('R' === parts[2]) { + 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(2 === cprArgs.length) { self.emit('cursor position report', cprArgs); } } + } else if((parts = RE_DEV_ATTR_RESPONSE_ANYWHERE.exec(s))) { + assert('c' === parts[2]); + var termClient = self.getTermClient(parts[1]); + if(termClient) { + self.term.termClient = termClient; + } } else if('\r' === s) { key.name = 'return'; } else if('\n' === s) { diff --git a/core/client_term.js b/core/client_term.js index 2e07ce50..67259ef2 100644 --- a/core/client_term.js +++ b/core/client_term.js @@ -30,6 +30,7 @@ function ClientTerminal(output) { var termType = 'unknown'; var termHeight = 0; var termWidth = 0; + var termClient = 'unknown'; // Raw values set by e.g. telnet NAWS, ENVIRONMENT, etc. this.env = {}; @@ -64,9 +65,11 @@ function ClientTerminal(output) { // SCOANSI // VT100 // XTERM + // * PuTTY // LINUX // QNX // SCREEN + // * ConnectBot // if(this.isANSI()) { this.outputEncoding = 'cp437'; @@ -75,6 +78,10 @@ function ClientTerminal(output) { 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'); } }); @@ -100,6 +107,17 @@ function ClientTerminal(output) { } } }); + + Object.defineProperty(this, 'termClient', { + get : function() { + return termClient; + }, + set : function(tc) { + termClient = tc; + + Log.debug( { termClient : this.termClient }, 'Set known terminal client'); + } + }); } ClientTerminal.prototype.isANSI = function() { diff --git a/core/connect.js b/core/connect.js index 79322df0..fb00a6b9 100644 --- a/core/connect.js +++ b/core/connect.js @@ -92,6 +92,13 @@ function connectEntry(client) { // :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 + // + // Some terminal clients can be detected using a nonstandard ANSI DSR + // + term.rawWrite(ansi.queryDeviceAttributes(0)); + + // :TODO: PuTTY will apparently respond with "PuTTY" if a CTRL-E is sent to it + // // If we don't yet know the client term width/height, // try with a nonstandard ANSI DSR type request. diff --git a/core/database.js b/core/database.js index 555510a7..377cfaf9 100644 --- a/core/database.js +++ b/core/database.js @@ -112,17 +112,17 @@ function createMessageBaseTables() { ); dbs.message.run( - 'CREATE TRIGGER message_before_update BEFORE UPDATE ON message BEGIN' + + 'CREATE TRIGGER IF NOT EXISTS message_before_update BEFORE UPDATE ON message BEGIN' + ' DELETE FROM message_fts WHERE docid=old.rowid;' + 'END;' + - 'CREATE TRIGGER message_before_delete BEFORE DELETE ON message BEGIN' + + 'CREATE TRIGGER IF NOT EXISTS message_before_delete BEFORE DELETE ON message BEGIN' + ' DELETE FROM message_fts WHERE docid=old.rowid;' + 'END;' + '' + - 'CREATE TRIGGER message_after_update AFTER UPDATE ON message BEGIN' + + 'CREATE TRIGGER IF NOT EXISTS message_after_update AFTER UPDATE ON message BEGIN' + ' INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);' + 'END;' + - 'CREATE TRIGGER message_after_insert AFTER INSERT ON message BEGIN' + + 'CREATE TRIGGER IF NOT EXISTS message_after_insert AFTER INSERT ON message BEGIN' + ' INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);' + 'END;' ); diff --git a/core/multi_line_edit_text_view.js b/core/multi_line_edit_text_view.js index c0de5ee0..d5567405 100644 --- a/core/multi_line_edit_text_view.js +++ b/core/multi_line_edit_text_view.js @@ -392,8 +392,9 @@ function MultiLineEditTextView(options) { var absPos; if(self.getTextLength(index) > self.dimens.width) { - console.log('textLen=' + self.getTextLength(index) + ' / ' + self.dimens.width + ' / ' + - JSON.stringify(self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col))) + //console.log('textLen=' + self.getTextLength(index) + ' / ' + self.dimens.width + ' / ' + + // JSON.stringify(self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col))) + // // Update word wrapping and |cursorOffset| if the cursor // was within the bounds of the wrapped text @@ -408,22 +409,27 @@ function MultiLineEditTextView(options) { self.redrawRows(self.cursorPos.row, self.dimens.height); if(!_.isUndefined(cursorOffset)) { - console.log('cursorOffset=' + cursorOffset) + //console.log('cursorOffset=' + cursorOffset) self.cursorBeginOfNextLine(); self.cursorPos.col += cursorOffset; self.client.term.rawWrite(ansi.right(cursorOffset)); } else { + //console.log('this path') + self.moveClientCusorToCursorPos(); + /* + self.cursorPos.row++; self.cursorPos.col = 1; // we just added 1 char absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col); console.log('absPos=' + JSON.stringify(absPos)) self.client.term.rawWrite(ansi.goto(absPos.row, absPos.col)); + */ } } else { // // We must only redraw from col -> end of current visible line // - console.log('textLen=' + self.getTextLength(index)) + //console.log('textLen=' + self.getTextLength(index)) absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col); self.client.term.write( ansi.hideCursor() + diff --git a/core/servers/telnet.js b/core/servers/telnet.js index 7725fc59..3c161d8f 100644 --- a/core/servers/telnet.js +++ b/core/servers/telnet.js @@ -477,6 +477,11 @@ function TelnetClient(input, output) { this.input.on('end', function() { self.emit('end'); }); + + this.input.on('error', function sockError(err) { + Log.debug(err); // :TODO: probably something better... + self.emit('end'); + }); } util.inherits(TelnetClient, baseClient.Client);