mirror of
https://github.com/NuSkooler/enigma-bbs.git
synced 2025-07-22 18:56:00 +02:00
* 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)
This commit is contained in:
parent
2b963ce6f9
commit
40e0b55424
7 changed files with 83 additions and 11 deletions
|
@ -73,6 +73,8 @@ var CONTROL = {
|
||||||
hideCursor : '?25l', // Nonstandard - cterm.txt
|
hideCursor : '?25l', // Nonstandard - cterm.txt
|
||||||
showCursor : '?25h', // Nonstandard - cterm.txt
|
showCursor : '?25h', // Nonstandard - cterm.txt
|
||||||
|
|
||||||
|
queryDeviceAttributes : 'c', // Nonstandard - cterm.txt
|
||||||
|
|
||||||
// :TODO: see https://code.google.com/p/conemu-maximus5/wiki/AnsiEscapeCodes
|
// :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
|
// apparently some terms can report screen size and text area via 18t and 19t
|
||||||
};
|
};
|
||||||
|
|
|
@ -60,7 +60,8 @@ function getIntArgArray(array) {
|
||||||
return 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_ANYWHERE = /(?:\u001b)([a-zA-Z0-9])/;
|
||||||
var RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$');
|
var RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$');
|
||||||
var RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:' + [
|
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_FUNCTION_KEYCODE_ANYWHERE.source,
|
||||||
RE_META_KEYCODE_ANYWHERE.source,
|
RE_META_KEYCODE_ANYWHERE.source,
|
||||||
RE_DSR_RESPONSE_ANYWHERE.source,
|
RE_DSR_RESPONSE_ANYWHERE.source,
|
||||||
|
RE_DEV_ATTR_RESPONSE_ANYWHERE.source,
|
||||||
/\u001b./.source
|
/\u001b./.source
|
||||||
].join('|'));
|
].join('|'));
|
||||||
|
|
||||||
|
@ -98,7 +100,7 @@ function Client(input, output) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
this.term = new term.ClientTerminal(this.output);
|
this.term = new term.ClientTerminal(this.output);
|
||||||
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' } };
|
||||||
|
|
||||||
//
|
//
|
||||||
// Peek at incoming |data| and emit events for any special
|
// 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
|
// * http://www.ansi-bbs.org/ansi-bbs-core-server.html
|
||||||
// * Christopher Jeffrey's Blessed library @ https://github.com/chjj/blessed/
|
// * 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) {
|
this.isMouseInput = function(data) {
|
||||||
return /\x1b\[M/.test(data) ||
|
return /\x1b\[M/.test(data) ||
|
||||||
/\u001b\[M([\x00\u0020-\uffff]{3})/.test(data) ||
|
/\u001b\[M([\x00\u0020-\uffff]{3})/.test(data) ||
|
||||||
|
@ -256,12 +284,18 @@ 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]) {
|
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(';'));
|
var cprArgs = getIntArgArray(parts[1].split(';'));
|
||||||
if(2 === cprArgs.length) {
|
if(2 === cprArgs.length) {
|
||||||
self.emit('cursor position report', cprArgs);
|
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) {
|
} else if('\r' === s) {
|
||||||
key.name = 'return';
|
key.name = 'return';
|
||||||
} else if('\n' === s) {
|
} else if('\n' === s) {
|
||||||
|
|
|
@ -30,6 +30,7 @@ function ClientTerminal(output) {
|
||||||
var termType = 'unknown';
|
var termType = 'unknown';
|
||||||
var termHeight = 0;
|
var termHeight = 0;
|
||||||
var termWidth = 0;
|
var termWidth = 0;
|
||||||
|
var termClient = 'unknown';
|
||||||
|
|
||||||
// Raw values set by e.g. telnet NAWS, ENVIRONMENT, etc.
|
// Raw values set by e.g. telnet NAWS, ENVIRONMENT, etc.
|
||||||
this.env = {};
|
this.env = {};
|
||||||
|
@ -64,9 +65,11 @@ function ClientTerminal(output) {
|
||||||
// SCOANSI
|
// SCOANSI
|
||||||
// VT100
|
// VT100
|
||||||
// XTERM
|
// XTERM
|
||||||
|
// * PuTTY
|
||||||
// LINUX
|
// LINUX
|
||||||
// QNX
|
// QNX
|
||||||
// SCREEN
|
// SCREEN
|
||||||
|
// * ConnectBot
|
||||||
//
|
//
|
||||||
if(this.isANSI()) {
|
if(this.isANSI()) {
|
||||||
this.outputEncoding = 'cp437';
|
this.outputEncoding = 'cp437';
|
||||||
|
@ -75,6 +78,10 @@ function ClientTerminal(output) {
|
||||||
this.outputEncoding = 'utf8';
|
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');
|
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() {
|
ClientTerminal.prototype.isANSI = function() {
|
||||||
|
|
|
@ -92,6 +92,13 @@ function connectEntry(client) {
|
||||||
// :TODO: Enthral for example queries cursor position & checks if it worked. This might be good
|
// :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
|
// :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,
|
// If we don't yet know the client term width/height,
|
||||||
// try with a nonstandard ANSI DSR type request.
|
// try with a nonstandard ANSI DSR type request.
|
||||||
|
|
|
@ -112,17 +112,17 @@ function createMessageBaseTables() {
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.message.run(
|
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;' +
|
' DELETE FROM message_fts WHERE docid=old.rowid;' +
|
||||||
'END;' +
|
'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;' +
|
' DELETE FROM message_fts WHERE docid=old.rowid;' +
|
||||||
'END;' +
|
'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);' +
|
' INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);' +
|
||||||
'END;' +
|
'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);' +
|
' INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);' +
|
||||||
'END;'
|
'END;'
|
||||||
);
|
);
|
||||||
|
|
|
@ -392,8 +392,9 @@ function MultiLineEditTextView(options) {
|
||||||
var absPos;
|
var absPos;
|
||||||
|
|
||||||
if(self.getTextLength(index) > self.dimens.width) {
|
if(self.getTextLength(index) > self.dimens.width) {
|
||||||
console.log('textLen=' + 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)))
|
// JSON.stringify(self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col)))
|
||||||
|
|
||||||
//
|
//
|
||||||
// Update word wrapping and |cursorOffset| if the cursor
|
// Update word wrapping and |cursorOffset| if the cursor
|
||||||
// was within the bounds of the wrapped text
|
// was within the bounds of the wrapped text
|
||||||
|
@ -408,22 +409,27 @@ function MultiLineEditTextView(options) {
|
||||||
self.redrawRows(self.cursorPos.row, self.dimens.height);
|
self.redrawRows(self.cursorPos.row, self.dimens.height);
|
||||||
|
|
||||||
if(!_.isUndefined(cursorOffset)) {
|
if(!_.isUndefined(cursorOffset)) {
|
||||||
console.log('cursorOffset=' + cursorOffset)
|
//console.log('cursorOffset=' + cursorOffset)
|
||||||
self.cursorBeginOfNextLine();
|
self.cursorBeginOfNextLine();
|
||||||
self.cursorPos.col += cursorOffset;
|
self.cursorPos.col += cursorOffset;
|
||||||
self.client.term.rawWrite(ansi.right(cursorOffset));
|
self.client.term.rawWrite(ansi.right(cursorOffset));
|
||||||
} else {
|
} else {
|
||||||
|
//console.log('this path')
|
||||||
|
self.moveClientCusorToCursorPos();
|
||||||
|
/*
|
||||||
|
|
||||||
self.cursorPos.row++;
|
self.cursorPos.row++;
|
||||||
self.cursorPos.col = 1; // we just added 1 char
|
self.cursorPos.col = 1; // we just added 1 char
|
||||||
absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col);
|
absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col);
|
||||||
console.log('absPos=' + JSON.stringify(absPos))
|
console.log('absPos=' + JSON.stringify(absPos))
|
||||||
self.client.term.rawWrite(ansi.goto(absPos.row, absPos.col));
|
self.client.term.rawWrite(ansi.goto(absPos.row, absPos.col));
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//
|
//
|
||||||
// We must only redraw from col -> end of current visible line
|
// 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);
|
absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col);
|
||||||
self.client.term.write(
|
self.client.term.write(
|
||||||
ansi.hideCursor() +
|
ansi.hideCursor() +
|
||||||
|
|
|
@ -477,6 +477,11 @@ function TelnetClient(input, output) {
|
||||||
this.input.on('end', function() {
|
this.input.on('end', function() {
|
||||||
self.emit('end');
|
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);
|
util.inherits(TelnetClient, baseClient.Client);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue