extend logic to OSC 12 cursor color

This commit is contained in:
Jörg Breitbart
2021-11-09 13:46:54 +01:00
parent 4be063be0f
commit 896f5a7e4c
6 changed files with 123 additions and 60 deletions
-7
View File
@@ -217,13 +217,6 @@ export namespace rgba {
rgba: channels.toRgba(r, g, b)
};
}
/**
* convert 0xRRGGBBAA to 0xAABBGGRR (32-bit representation on LE systems)
*/
export function toABGR32(rgba: number): number {
return ((rgba & 0xFF) << 24 | (rgba >>> 8 & 0xFF) << 16 | (rgba >>> 16 & 0xFF) << 8 | rgba >>> 24 & 0xFF) >>> 0;
}
}
export function toPaddedHex(c: number): string {
+29 -8
View File
@@ -178,23 +178,44 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
}
/**
* Handle color event from inputhandler for OSC 4 | 10 | 11 | 12.
* An event from OSC 4 may contain multiple set or report requests,
* while OSC 10 | 11 | 12 create single requests.
*/
private _handleColorEvent(event: IColorEvent): void {
if (!this._colorManager) return;
for (const req of event) {
let acc: 'foreground' | 'background' | 'cursor' | 'ansi' | undefined = undefined;
let ident = '';
switch (req.index) {
case ColorIndex.FOREGROUND: // OSC 10
if (req.color) this._colorManager.colors.foreground = rgba.toColor(...req.color);
else this.coreService.triggerDataEvent(`${C0.ESC}]10;${toRgbString(color.toColorRGB(this._colorManager.colors.foreground))}${C0.BEL}`);
acc = 'foreground';
ident = '10';
break;
case ColorIndex.BACKGROUND: // OSC 11
if (req.color) this._colorManager.colors.background = rgba.toColor(...req.color);
else this.coreService.triggerDataEvent(`${C0.ESC}]11;${toRgbString(color.toColorRGB(this._colorManager.colors.background))}${C0.BEL}`);
acc = 'background';
ident = '11';
break;
case ColorIndex.CURSOR: // OSC 12
acc = 'cursor';
ident = '12';
break;
default: // OSC 4
if (0 <= req.index && req.index < 256) {
if (req.color) this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
else this.coreService.triggerDataEvent(`${C0.ESC}]4;${req.index};${toRgbString(color.toColorRGB(this._colorManager.colors.ansi[req.index]))}${C0.BEL}`);
}
// we can skip the [0..255] range check here (already done in inputhandler)
acc = 'ansi';
ident = '4;' + req.index;
}
if (acc) {
if (req.color) {
if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
else this._colorManager.colors[acc] = rgba.toColor(...req.color);
} else {
const channels = color.toColorRGB(acc === 'ansi'
? this._colorManager.colors.ansi[req.index]
: this._colorManager.colors[acc]);
this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C0.BEL}`);
}
}
}
this._renderService?.setColors(this._colorManager.colors);
+36 -10
View File
@@ -1902,16 +1902,16 @@ describe('InputHandler', () => {
stack.length = 0;
});
it('10: should create appropriate events', async () => {
it('10: FG set & query events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single foreground query --> color undefined
await inputHandler.parseP('\x1b]10;?\x07');
assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND }]]);
stack.length = 0;
// OSC with multiple values maps to OSC 10 & OSC 11
// OSC with multiple values maps to OSC 10 & OSC 11 & OSC 12
await inputHandler.parseP('\x1b]10;?;?;?;?\x07');
assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND }], [{ index: ColorIndex.BACKGROUND }]]);
assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND }], [{ index: ColorIndex.BACKGROUND }], [{ index: ColorIndex.CURSOR }]]);
stack.length = 0;
// set foreground color events
await inputHandler.parseP('\x1b]10;rgb:01/02/03\x07');
@@ -1920,23 +1920,24 @@ describe('InputHandler', () => {
await inputHandler.parseP('\x1b]10;#aabbcc\x07');
assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND, color: [170, 187, 204] }]]);
stack.length = 0;
// set FG and BG at once
await inputHandler.parseP('\x1b]10;rgb:aa/bb/cc;#001122\x07');
// set FG, BG and cursor color at once
await inputHandler.parseP('\x1b]10;rgb:aa/bb/cc;#001122;rgb:12/34/56\x07');
assert.deepEqual(stack, [
[{ index: ColorIndex.FOREGROUND, color: [170, 187, 204] }],
[{ index: ColorIndex.BACKGROUND, color: [0, 17, 34] }]
[{ index: ColorIndex.BACKGROUND, color: [0, 17, 34] }],
[{ index: ColorIndex.CURSOR, color: [18, 52, 86] }]
]);
});
it('11: should create appropriate events', async () => {
it('11: BG set & query events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single foreground query --> color undefined
// single background query --> color undefined
await inputHandler.parseP('\x1b]11;?\x07');
assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }]]);
stack.length = 0;
// OSC 11 with multiple values creates only one BG event
// OSC 11 with multiple values creates only BG and cursor event
await inputHandler.parseP('\x1b]11;?;?;?;?\x07');
assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }]]);
assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }], [{ index: ColorIndex.CURSOR }]]);
stack.length = 0;
// set background color events
await inputHandler.parseP('\x1b]11;rgb:01/02/03\x07');
@@ -1944,6 +1945,31 @@ describe('InputHandler', () => {
stack.length = 0;
await inputHandler.parseP('\x1b]11;#aabbcc\x07');
assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND, color: [170, 187, 204] }]]);
stack.length = 0;
// set BG and cursor color at once
await inputHandler.parseP('\x1b]11;#001122;rgb:12/34/56\x07');
assert.deepEqual(stack, [
[{ index: ColorIndex.BACKGROUND, color: [0, 17, 34] }],
[{ index: ColorIndex.CURSOR, color: [18, 52, 86] }]
]);
});
it('12: cursor color set & query events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single cursor query --> color undefined
await inputHandler.parseP('\x1b]12;?\x07');
assert.deepEqual(stack, [[{ index: ColorIndex.CURSOR }]]);
stack.length = 0;
// OSC 12 with multiple values creates only cursor event
await inputHandler.parseP('\x1b]12;?;?;?;?\x07');
assert.deepEqual(stack, [[{ index: ColorIndex.CURSOR }]]);
stack.length = 0;
// set cursor color events
await inputHandler.parseP('\x1b]12;rgb:01/02/03\x07');
assert.deepEqual(stack, [[{ index: ColorIndex.CURSOR, color: [1, 2, 3] }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]12;#aabbcc\x07');
assert.deepEqual(stack, [[{ index: ColorIndex.CURSOR, color: [170, 187, 204] }]]);
});
});
+48 -33
View File
@@ -408,6 +408,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// 11 - Change VT100 text background color to Pt.
this._parser.registerOscHandler(11, new OscHandler(data => this.setOrReportBgColor(data)));
// 12 - Change text cursor color to Pt.
this._parser.registerOscHandler(12, new OscHandler(data => this.setOrReportCursorColor(data)));
// 13 - Change mouse foreground color to Pt.
// 14 - Change mouse background color to Pt.
// 15 - Change Tektronix foreground color to Pt.
@@ -2846,8 +2847,9 @@ export class InputHandler extends Disposable implements IInputHandler {
* OSC 4; <num> ; <text> ST (set ANSI color <num> to <text>)
*
* @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; c ; spec BEL" "Change color number `c` to the color specified by `spec`."
* `c` is the color index between 0 and 255. `spec` color format is 'rgb:hh/hh/hh' where `h` are hexadecimal digits.
* There may be multipe c ; spec elements present in the same instruction, e.g. 1;rgb:10/20/30;2;rgb:a0/b0/c0.
* `c` is the color index between 0 and 255. The color format of `spec` is derived from `XParseColor` (see OSC 10 for supported formats).
* There may be multipe `c ; spec` pairs present in the same instruction.
* If `spec` contains `?` the terminal returns a sequence with the currently set color.
*/
public setOrReportIndexedColor(data: string): boolean {
const event: IColorEvent = [];
@@ -2856,12 +2858,15 @@ export class InputHandler extends Disposable implements IInputHandler {
const idx = slots.shift() as string;
const spec = slots.shift() as string;
if (/^\d+$/.exec(idx)) {
if (spec === '?') {
event.push({ index: parseInt(idx) });
} else {
const color = parseColor(spec);
if (color) {
event.push({ index: parseInt(idx), color });
const index = parseInt(idx);
if (0 <= index && index < 256) {
if (spec === '?') {
event.push({ index });
} else {
const color = parseColor(spec);
if (color) {
event.push({ index, color });
}
}
}
}
@@ -2872,6 +2877,30 @@ export class InputHandler extends Disposable implements IInputHandler {
return true;
}
// special colors - OSC 10 | 11 | 12
private _specialColors = [ColorIndex.FOREGROUND, ColorIndex.BACKGROUND, ColorIndex.CURSOR];
/**
* Apply colors requests for special colors in OSC 10 | 11 | 12.
* Since these commands are stacking from multiple parameters,
* we handle them in a loop with an entry offset to `_specialColors`.
*/
private _setOrReportSpecialColor(data: string, offset: number): boolean {
const slots = data.split(';');
for (let i = 0; i < slots.length; ++i, ++offset) {
if (offset >= this._specialColors.length) break;
if (slots[i] === '?') {
this._onColor.fire([{ index: this._specialColors[offset] }]);
} else {
const color = parseColor(slots[i]);
if (color) {
this._onColor.fire([{ index: this._specialColors[offset], color }]);
}
}
}
return true;
}
/**
* OSC 10 ; <xcolor name>|<?> ST - set or query default foreground color
*
@@ -2895,21 +2924,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* Therefore stacking multiple `Pt` separated by `;` only works for the first two entries.
*/
public setOrReportFgColor(data: string): boolean {
// Note: data may contain multiple values separated with ; mapping to OSC 10 - 19
const slots = data.split(';');
if (slots[0] === '?') {
this._onColor.fire([{ index: ColorIndex.FOREGROUND }]);
} else {
const color = parseColor(slots[0]);
if (color) {
this._onColor.fire([{ index: ColorIndex.FOREGROUND, color }]);
}
}
// forward second slot to OSC 11 (higher slots are not supported)
if (slots.length > 1) {
this.setOrReportBgColor(slots[1]);
}
return true;
return this._setOrReportSpecialColor(data, 0);
}
/**
@@ -2918,16 +2933,16 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y OSC 11 "Set or query default background color" "OSC 11 ; Pt BEL" "Same as OSC 10, but for default background."
*/
public setOrReportBgColor(data: string): boolean {
const slots = data.split(';');
if (slots[0] === '?') {
this._onColor.fire([{ index: ColorIndex.BACKGROUND }]);
} else {
const color = parseColor(slots[0]);
if (color) {
this._onColor.fire([{ index: ColorIndex.BACKGROUND, color }]);
}
}
return true;
return this._setOrReportSpecialColor(data, 1);
}
/**
* OSC 12 ; <xcolor name>|<?> ST - set or query default cursor color
*
* @vt: #Y OSC 12 "Set or query default cursor color" "OSC 12 ; Pt BEL" "Same as OSC 10, but for default cursor color."
*/
public setOrReportCursorColor(data: string): boolean {
return this._setOrReportSpecialColor(data, 2);
}
/**
+4 -2
View File
@@ -348,10 +348,11 @@ export interface IWindowOptions {
setWinLines?: boolean;
}
// color events from common, used for OSC 4/10/11
// color events from common, used for OSC 4/10/11/12
export const enum ColorIndex {
FOREGROUND = 256,
BACKGROUND = 257
BACKGROUND = 257,
CURSOR = 258
}
export interface IColorReportRequest {
index: ColorIndex;
@@ -431,6 +432,7 @@ export interface IInputHandler {
/** OSC 4 */ setOrReportIndexedColor(data: string): boolean;
/** OSC 10 */ setOrReportFgColor(data: string): boolean;
/** OSC 11 */ setOrReportBgColor(data: string): boolean;
/** OSC 12 */ setOrReportCursorColor(data: string): boolean;
/** ESC E */ nextLine(): boolean;
/** ESC = */ keypadApplicationMode(): boolean;
+6
View File
@@ -469,6 +469,12 @@ describe('InputHandler Integration Tests', function(): void {
await writeSync(page, '\x1b]11;#000000\x07\x1b]11;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07', '\x1b]11;rgb:0000/0000/0000\x07']);
});
it('set & query cursor color', async () => {
await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']);
await writeSync(page, '\x1b]12;#ffffff\x07\x1b]12;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07', '\x1b]12;rgb:ffff/ffff/ffff\x07']);
});
it('set & query FG & BG color in one call', async () => {
await writeSync(page, '\x1b]10;#123456;rgb:aa/bb/cc\x07\x1b]10;?;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x07', '\x1b]11;rgb:aaaa/bbbb/cccc\x07']);