SyncTERM Font Support Detection
* Attempt to detect SyncTERM font support using DSR/CPR * If a terminal doesn't support DSR/CPR, assume it doesn't support SyncTERM fonts, either. * Support is also disabled if term simply doesn't ignore the ESC sequence, either
This commit is contained in:
parent
ac010f2ca4
commit
15c3c87500
|
@ -59,8 +59,8 @@ exports.resetScreen = resetScreen;
|
||||||
exports.normal = normal;
|
exports.normal = normal;
|
||||||
exports.goHome = goHome;
|
exports.goHome = goHome;
|
||||||
exports.disableVT100LineWrapping = disableVT100LineWrapping;
|
exports.disableVT100LineWrapping = disableVT100LineWrapping;
|
||||||
exports.setSyncTERMFont = setSyncTERMFont;
|
exports.setSyncTermFont = setSyncTermFont;
|
||||||
exports.getSyncTERMFontFromAlias = getSyncTERMFontFromAlias;
|
exports.getSyncTermFontFromAlias = getSyncTermFontFromAlias;
|
||||||
exports.setSyncTermFontWithAlias = setSyncTermFontWithAlias;
|
exports.setSyncTermFontWithAlias = setSyncTermFontWithAlias;
|
||||||
exports.setCursorStyle = setCursorStyle;
|
exports.setCursorStyle = setCursorStyle;
|
||||||
exports.setEmulatedBaudRate = setEmulatedBaudRate;
|
exports.setEmulatedBaudRate = setEmulatedBaudRate;
|
||||||
|
@ -320,7 +320,7 @@ const FONT_ALIAS_TO_SYNCTERM_MAP = {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function setSyncTERMFont(name, fontPage) {
|
function setSyncTermFont(name, fontPage) {
|
||||||
const p1 = miscUtil.valueWithDefault(fontPage, 0);
|
const p1 = miscUtil.valueWithDefault(fontPage, 0);
|
||||||
|
|
||||||
assert(p1 >= 0 && p1 <= 3);
|
assert(p1 >= 0 && p1 <= 3);
|
||||||
|
@ -333,13 +333,13 @@ function setSyncTERMFont(name, fontPage) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSyncTERMFontFromAlias(alias) {
|
function getSyncTermFontFromAlias(alias) {
|
||||||
return FONT_ALIAS_TO_SYNCTERM_MAP[alias.toLowerCase().replace(/ /g, '_')];
|
return FONT_ALIAS_TO_SYNCTERM_MAP[alias.toLowerCase().replace(/ /g, '_')];
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSyncTermFontWithAlias(nameOrAlias) {
|
function setSyncTermFontWithAlias(nameOrAlias) {
|
||||||
nameOrAlias = getSyncTERMFontFromAlias(nameOrAlias) || nameOrAlias;
|
nameOrAlias = getSyncTermFontFromAlias(nameOrAlias) || nameOrAlias;
|
||||||
return setSyncTERMFont(nameOrAlias);
|
return setSyncTermFont(nameOrAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEC_CURSOR_STYLE = {
|
const DEC_CURSOR_STYLE = {
|
||||||
|
|
34
core/art.js
34
core/art.js
|
@ -351,23 +351,25 @@ function display(client, art, options, cb) {
|
||||||
});
|
});
|
||||||
|
|
||||||
let initSeq = '';
|
let initSeq = '';
|
||||||
if(options.font) {
|
if (client.term.syncTermFontsEnabled) {
|
||||||
initSeq = ansi.setSyncTermFontWithAlias(options.font);
|
if(options.font) {
|
||||||
} else if(options.sauce) {
|
initSeq = ansi.setSyncTermFontWithAlias(options.font);
|
||||||
let fontName = getFontNameFromSAUCE(options.sauce);
|
} else if(options.sauce) {
|
||||||
if(fontName) {
|
let fontName = getFontNameFromSAUCE(options.sauce);
|
||||||
fontName = ansi.getSyncTERMFontFromAlias(fontName);
|
if(fontName) {
|
||||||
}
|
fontName = ansi.getSyncTermFontFromAlias(fontName);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Set SyncTERM font if we're switching only. Most terminals
|
// Set SyncTERM font if we're switching only. Most terminals
|
||||||
// that support this ESC sequence can only show *one* font
|
// that support this ESC sequence can only show *one* font
|
||||||
// at a time. This applies to detection only (e.g. SAUCE).
|
// at a time. This applies to detection only (e.g. SAUCE).
|
||||||
// If explicit, we'll set it no matter what (above)
|
// If explicit, we'll set it no matter what (above)
|
||||||
//
|
//
|
||||||
if(fontName && client.term.currentSyncFont != fontName) {
|
if(fontName && client.term.currentSyncFont != fontName) {
|
||||||
client.term.currentSyncFont = fontName;
|
client.term.currentSyncFont = fontName;
|
||||||
initSeq = ansi.setSyncTERMFont(fontName);
|
initSeq = ansi.setSyncTermFont(fontName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ function ClientTerminal(output) {
|
||||||
// convert line feeds such as \n -> \r\n
|
// convert line feeds such as \n -> \r\n
|
||||||
this.convertLF = true;
|
this.convertLF = true;
|
||||||
|
|
||||||
|
this.syncTermFontsEnabled = false;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Some terminal we handle specially
|
// Some terminal we handle specially
|
||||||
// They can also be found in this.env{}
|
// They can also be found in this.env{}
|
||||||
|
|
256
core/connect.js
256
core/connect.js
|
@ -12,6 +12,28 @@ const async = require('async');
|
||||||
|
|
||||||
exports.connectEntry = connectEntry;
|
exports.connectEntry = connectEntry;
|
||||||
|
|
||||||
|
const withCursorPositionReport = (client, cprHandler, failMessage, cb) => {
|
||||||
|
let giveUpTimer;
|
||||||
|
|
||||||
|
const done = function(err) {
|
||||||
|
client.removeListener('cursor position report', cprListener);
|
||||||
|
clearTimeout(giveUpTimer);
|
||||||
|
return cb(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cprListener = (pos) => {
|
||||||
|
cprHandler(pos);
|
||||||
|
return done(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
client.once('cursor position report', cprListener);
|
||||||
|
|
||||||
|
// give up after 2s
|
||||||
|
giveUpTimer = setTimeout( () => {
|
||||||
|
return done(Errors.General(failMessage));
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
function ansiDiscoverHomePosition(client, cb) {
|
function ansiDiscoverHomePosition(client, cb) {
|
||||||
//
|
//
|
||||||
// We want to find the home position. ANSI-BBS and most terminals
|
// We want to find the home position. ANSI-BBS and most terminals
|
||||||
|
@ -19,47 +41,34 @@ function ansiDiscoverHomePosition(client, cb) {
|
||||||
// think of home as 0,0. If this is the case, we need to offset
|
// think of home as 0,0. If this is the case, we need to offset
|
||||||
// our positioning to accommodate for such.
|
// our positioning to accommodate for such.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
if( !Config().term.checkAnsiHomePosition ) {
|
if( !Config().term.checkAnsiHomePosition ) {
|
||||||
// Skip (and assume 1,1) if the home position check is disabled.
|
// Skip (and assume 1,1) if the home position check is disabled.
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const done = (err) => {
|
withCursorPositionReport(
|
||||||
client.removeListener('cursor position report', cprListener);
|
client,
|
||||||
clearTimeout(giveUpTimer);
|
pos => {
|
||||||
return cb(err);
|
const [h, w] = pos;
|
||||||
};
|
|
||||||
|
|
||||||
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
|
// We expect either 0,0, or 1,1. Anything else will be filed as bad data
|
||||||
//
|
//
|
||||||
client.log.info('Setting CPR offset to 1');
|
if(h > 1 || w > 1) {
|
||||||
client.cprOffset = 1;
|
return client.log.warn( { height : h, width : w }, 'Ignoring ANSI home position CPR due to unexpected values');
|
||||||
}
|
}
|
||||||
|
|
||||||
return done(null);
|
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.once('cursor position report', cprListener);
|
//
|
||||||
|
client.log.info('Setting CPR offset to 1');
|
||||||
const giveUpTimer = setTimeout( () => {
|
client.cprOffset = 1;
|
||||||
return done(Errors.General('Giving up on home position CPR'));
|
}
|
||||||
}, 3000); // 3s
|
},
|
||||||
|
'ANSI query home position timed out',
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
|
||||||
client.term.write(`${ansi.goHome()}${ansi.queryPos()}`); // go home, query pos
|
client.term.write(`${ansi.goHome()}${ansi.queryPos()}`); // go home, query pos
|
||||||
}
|
}
|
||||||
|
@ -82,56 +91,74 @@ function ansiAttemptDetectUTF8(client, cb) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
let posStage = 1;
|
|
||||||
let initialPosition;
|
let initialPosition;
|
||||||
let giveUpTimer;
|
|
||||||
|
|
||||||
const giveUp = () => {
|
|
||||||
client.removeListener('cursor position report', cprListener);
|
|
||||||
clearTimeout(giveUpTimer);
|
|
||||||
return cb(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ASCIIPortion = ' Character encoding detection ';
|
const ASCIIPortion = ' Character encoding detection ';
|
||||||
|
|
||||||
const cprListener = (pos) => {
|
withCursorPositionReport(
|
||||||
switch(posStage) {
|
client,
|
||||||
case 1 :
|
pos => {
|
||||||
posStage = 2;
|
initialPosition = pos;
|
||||||
|
|
||||||
initialPosition = pos;
|
withCursorPositionReport(client,
|
||||||
clearTimeout(giveUpTimer);
|
pos => {
|
||||||
|
const [_, w] = pos;
|
||||||
giveUpTimer = setTimeout( () => {
|
const len = w - initialPosition[1];
|
||||||
return giveUp();
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
client.once('cursor position report', cprListener);
|
|
||||||
client.term.rawWrite(`\u9760${ASCIIPortion}\u9760`); // Unicode skulls on each side
|
|
||||||
client.term.rawWrite(ansi.queryPos());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2 :
|
|
||||||
{
|
|
||||||
clearTimeout(giveUpTimer);
|
|
||||||
const len = pos[1] - initialPosition[1];
|
|
||||||
if(!isNaN(len) && len >= ASCIIPortion.length + 6) { // CP437 displays 3 chars each Unicode skull
|
if(!isNaN(len) && len >= ASCIIPortion.length + 6) { // CP437 displays 3 chars each Unicode skull
|
||||||
client.log.info('Terminal identified as UTF-8 but does not appear to be. Overriding to "ansi".');
|
client.log.info('Terminal identified as UTF-8 but does not appear to be. Overriding to "ansi".');
|
||||||
client.setTermType('ansi');
|
client.setTermType('ansi');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
return cb(null);
|
'Detect UTF-8 stage 2 timed out',
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
|
||||||
|
client.term.rawWrite(`\u9760${ASCIIPortion}\u9760`); // Unicode skulls on each side
|
||||||
|
client.term.rawWrite(ansi.queryPos());
|
||||||
|
},
|
||||||
|
'Detect UTF-8 stage 1 timed out',
|
||||||
|
err => {
|
||||||
|
if (err) {
|
||||||
|
// bailing early
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
|
|
||||||
giveUpTimer = setTimeout( () => {
|
client.term.rawWrite(`${ansi.goHome()}${ansi.queryPos()}`);
|
||||||
return giveUp();
|
}
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
|
const ansiQuerySyncTermFontSupport = (client, cb) => {
|
||||||
|
// If we know it's SyncTERM, then good to go
|
||||||
|
if (client.term.termClient === 'cterm') {
|
||||||
|
client.term.syncTermFontsEnabled = true;
|
||||||
|
return cb(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some terminals do not properly ignore an unknown ESC sequence for
|
||||||
|
// setting SyncTERM style fonts. Some draw only a 'D' while others
|
||||||
|
// show more of the sequence
|
||||||
|
//
|
||||||
|
// If the cursor remains at 1,1, they are at least ignoring it, if not
|
||||||
|
// supporting it.
|
||||||
|
//
|
||||||
|
// We also need CPR support. Since all known terminals that support
|
||||||
|
// SyncTERM fonts also support CPR, this works out.
|
||||||
|
//
|
||||||
|
withCursorPositionReport(
|
||||||
|
client,
|
||||||
|
pos => {
|
||||||
|
const [_, w] = pos;
|
||||||
|
if (w === 1) {
|
||||||
|
// cursor didn't move
|
||||||
|
client.log.info('Client supports SyncTERM fonts or properly ignores unknown ESC sequence');
|
||||||
|
client.term.syncTermFontsEnabled = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'SyncTERM font detection timed out',
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
|
||||||
client.once('cursor position report', cprListener);
|
client.term.rawWrite(`${ansi.goto(1, 1)}${ansi.setSyncTermFont('cp437')}${ansi.queryPos()}`);
|
||||||
client.term.rawWrite(ansi.goHome() + ansi.queryPos());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ansiQueryTermSizeIfNeeded(client, cb) {
|
function ansiQueryTermSizeIfNeeded(client, cb) {
|
||||||
|
@ -139,56 +166,43 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const done = function(err) {
|
withCursorPositionReport(
|
||||||
client.removeListener('cursor position report', cprListener);
|
client,
|
||||||
clearTimeout(giveUpTimer);
|
pos => {
|
||||||
return cb(err);
|
//
|
||||||
};
|
// If we've already found out, disregard
|
||||||
|
//
|
||||||
|
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const cprListener = function(pos) {
|
//
|
||||||
//
|
// NetRunner for example gives us 1x1 here. Not really useful. Ignore
|
||||||
// If we've already found out, disregard
|
// values that seem obviously bad. Included in the set is the explicit
|
||||||
//
|
// 999x999 values we asked to move to.
|
||||||
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
//
|
||||||
return done(null);
|
const [h, w] = pos;
|
||||||
}
|
if(h < 10 || h === 999 || w < 10 || w === 999) {
|
||||||
|
return client.log.warn(
|
||||||
|
{ height : h, width : w },
|
||||||
|
'Ignoring ANSI CPR screen size query response due to non-sane values');
|
||||||
|
}
|
||||||
|
|
||||||
const h = pos[0];
|
client.term.termHeight = h;
|
||||||
const w = pos[1];
|
client.term.termWidth = w;
|
||||||
|
|
||||||
//
|
client.log.debug(
|
||||||
// NetRunner for example gives us 1x1 here. Not really useful. Ignore
|
{
|
||||||
// values that seem obviously bad. Included in the set is the explicit
|
termWidth : client.term.termWidth,
|
||||||
// 999x999 values we asked to move to.
|
termHeight : client.term.termHeight,
|
||||||
//
|
source : 'ANSI CPR'
|
||||||
if(h < 10 || h === 999 || w < 10 || w === 999) {
|
},
|
||||||
client.log.warn(
|
'Window size updated'
|
||||||
{ height : h, width : w },
|
);
|
||||||
'Ignoring ANSI CPR screen size query response due to non-sane values');
|
},
|
||||||
return done(Errors.Invalid('Term size <= 10 considered invalid'));
|
'No term size established by CPR within timeout',
|
||||||
}
|
cb
|
||||||
|
);
|
||||||
client.term.termHeight = h;
|
|
||||||
client.term.termWidth = w;
|
|
||||||
|
|
||||||
client.log.debug(
|
|
||||||
{
|
|
||||||
termWidth : client.term.termWidth,
|
|
||||||
termHeight : client.term.termHeight,
|
|
||||||
source : 'ANSI CPR'
|
|
||||||
},
|
|
||||||
'Window size updated'
|
|
||||||
);
|
|
||||||
|
|
||||||
return done(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
client.once('cursor position report', cprListener);
|
|
||||||
|
|
||||||
// give up after 2s
|
|
||||||
const giveUpTimer = setTimeout( () => {
|
|
||||||
return done(Errors.General('No term size established by CPR within timeout'));
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
// Start the process:
|
// Start the process:
|
||||||
// 1 - Ask to goto 999,999 -- a very much "bottom right" (generally 80x25 for example
|
// 1 - Ask to goto 999,999 -- a very much "bottom right" (generally 80x25 for example
|
||||||
|
@ -223,12 +237,11 @@ function connectEntry(client, nextMenu) {
|
||||||
[
|
[
|
||||||
function basicPrepWork(callback) {
|
function basicPrepWork(callback) {
|
||||||
term.rawWrite(ansi.queryDeviceAttributes(0));
|
term.rawWrite(ansi.queryDeviceAttributes(0));
|
||||||
|
term.write('\nDetecting client features...');
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function discoverHomePosition(callback) {
|
function discoverHomePosition(callback) {
|
||||||
ansiDiscoverHomePosition(client, () => {
|
return ansiDiscoverHomePosition(client, callback);
|
||||||
return callback(null); // we try to continue anyway
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
function queryTermSizeByNonStandardAnsi(callback) {
|
function queryTermSizeByNonStandardAnsi(callback) {
|
||||||
ansiQueryTermSizeIfNeeded(client, err => {
|
ansiQueryTermSizeIfNeeded(client, err => {
|
||||||
|
@ -254,7 +267,10 @@ function connectEntry(client, nextMenu) {
|
||||||
},
|
},
|
||||||
function checkUtf8IfNeeded(callback) {
|
function checkUtf8IfNeeded(callback) {
|
||||||
return ansiAttemptDetectUTF8(client, callback);
|
return ansiAttemptDetectUTF8(client, callback);
|
||||||
}
|
},
|
||||||
|
function querySyncTERMFontSupport(callback) {
|
||||||
|
return ansiQuerySyncTermFontSupport(client, callback);
|
||||||
|
},
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
prepareTerminal(term);
|
prepareTerminal(term);
|
||||||
|
|
|
@ -199,7 +199,8 @@ exports.getModule = class TelnetBridgeModule extends MenuModule {
|
||||||
self.client.removeListener('key press', connectionKeyPressHandler);
|
self.client.removeListener('key press', connectionKeyPressHandler);
|
||||||
self.client.log.info(connectOpts, 'Telnet bridge connection established');
|
self.client.log.info(connectOpts, 'Telnet bridge connection established');
|
||||||
|
|
||||||
if(self.config.font) {
|
// put the font back how it was prior, if fonts are enabled
|
||||||
|
if(self.client.term.syncTermFontsEnabled && self.config.font) {
|
||||||
self.client.term.rawWrite(setSyncTermFontWithAlias(self.config.font));
|
self.client.term.rawWrite(setSyncTermFontWithAlias(self.config.font));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue