From b3750bed2d209713a45e656c0c1cb4db3e60fb8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 24 Aug 2019 11:47:46 +0200 Subject: [PATCH 01/15] early hack --- src/InputHandler.ts | 97 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 9006777286..b9f5aee596 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -217,6 +217,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params)); this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params)); this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params)); + this._parser.setCsiHandler({final: 't'}, params => this.manipulateWindowOptions(params)); this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params)); /** @@ -1951,6 +1952,102 @@ export class InputHandler extends Disposable implements IInputHandler { } } + /** + * CSI Ps ; Ps ; Ps t - Various window manipulations and reports (xterm) + * Ps = 1 -> De-iconify window. not supported + * Ps = 2 -> Iconify window. not supported + * Ps = 3 ; x ; y -> Move window to [x, y]. not supported + * Ps = 4 ; height ; width TBD + * Resize the xterm window to given height and width in pixels. + * Omitted parameters reuse the current height or width. + * Zero parameters use the display's height or width. + * Ps = 5 not supported + * Raise the xterm window to the front of the stacking order. + * Ps = 6 not supported + * Lower the xterm window to the bottom of the stacking order. + * Ps = 7 TBD + * Refresh the xterm window. + * Ps = 8 ; height ; width TBD + * Resize the text area to given height and width in characters. + * Omitted parameters reuse the current height or width. + * Zero parameters use the display's height or width. + * Ps = 9 ; 0 -> Restore maximized window. TBD + * Ps = 9 ; 1 TBD + * Maximize window (i.e., resize to screen size). + * Ps = 9 ; 2 -> Maximize window vertically. TBD + * Ps = 9 ; 3 -> Maximize window horizontally. TBD + * Ps = 10 ; 0 -> Undo full-screen mode. TBD + * Ps = 10 ; 1 -> Change to full-screen. TBD + * Ps = 10 ; 2 -> Toggle full-screen. TBD + * Ps = 11 not supported (always report non-iconified?) + * Report xterm window state. + * If the xterm window is non-iconified, it returns CSI 1 t . + * If the xterm window is iconified, it returns CSI 2 t . + * Ps = 13 -> Report xterm window position. TBD + * Note: X Toolkit positions can be negative, but the reported + * values are unsigned, in the range 0-65535. Negative values + * correspond to 32768-65535. + * Result is CSI 3 ; x ; y t + * Ps = 13 ; 2 TBD + * Report xterm text-area position. Result is CSI 3 ; x ; y t + * Ps = 1 4 TBD + * Report xterm text area size in pixels. Result is CSI 4 ; height ; width t + * Ps = 14 ; 2 TBD + * Report xterm window size in pixels. + * Normally xterm's window is larger than its text area, since it + * includes the frame (or decoration) applied by the window manager, + * as well as the area used by a scroll-bar. Result is CSI 4 ; height ; width t + * Ps = 15 TBD + * Report size of the screen in pixels. Result is CSI 5 ; height ; width t + * Ps = 16 TBD + * Report xterm character cell size in pixels. Result is CSI 6 ; height ; width t + * Ps = 18 TBD + * Report the size of the text area in characters. Result is CSI 8 ; height ; width t + * Ps = 19 TBD + * Report the size of the screen in characters. Result is CSI 9 ; height ; width t + * Ps = 20 TBD + * Report xterm window's icon label. Result is OSC L label ST + * Ps = 21 TBD + * Report xterm window's title. Result is OSC l label ST + * Ps = 22 ; 0 -> Save xterm icon and window title on stack. TBD + * Ps = 22 ; 1 -> Save xterm icon title on stack. TBD + * Ps = 22 ; 2 -> Save xterm window title on stack. TBD + * Ps = 23 ; 0 -> Restore xterm icon and window title from stack. TBD + * Ps = 23 ; 1 -> Restore xterm icon title from stack. TBD + * Ps = 23 ; 2 -> Restore xterm window title from stack. TBD + * Ps >= 24 TBD + * Resize to Ps lines (DECSLPP), VT340 and VT420. xterm adapts this by resizing its window. + */ + public manipulateWindowOptions(params?: IParams): void { + console.log(params); + switch (params.params[0]) { + case 8: + if (params.length === 3) { + // TODO: 0/1 support + const cols = Math.max(2, params.params[2]); + const rows = Math.max(2, params.params[1]); + // make it work with demo thus shamelessly taken from demo's client.ts + const width = (cols * (this._terminal as any)._renderService.dimensions.actualCellWidth + this._terminal.viewport.scrollBarWidth).toString() + 'px'; + const height = (rows * (this._terminal as any)._renderService.dimensions.actualCellHeight).toString() + 'px'; + (this._terminal as any)._parent.style.width = width; + (this._terminal as any)._parent.style.height = height; + const term = Function('return this.term;')(); + // move fit into browser codebase? + for (const addon of (term as any)._addonManager._addons) { + if (`${addon.instance.constructor}`.indexOf('FitAddon') !== -1) { + addon.instance.fit(); + } + } + } + break; + case 18: + const height = this._bufferService.rows; + const width = this._bufferService.cols; + this._coreService.triggerDataEvent(`${C0.ESC}[8;${height};${width}t`); + break; + } + } + /** * CSI s From da3ae79c37932271e322f3c7c4b125b07cb06ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 29 Oct 2019 13:57:36 +0100 Subject: [PATCH 02/15] implement 14, 16, 18, 20, 21, 22, 23; restrict DECCOLM to 24 --- demo/client.ts | 5 +- src/InputHandler.test.ts | 147 +++++++++++++++++ src/InputHandler.ts | 229 +++++++++++++++----------- src/Terminal.ts | 6 +- src/TestUtils.test.ts | 1 - src/Types.d.ts | 1 - src/common/services/OptionsService.ts | 48 +++++- src/common/services/Services.ts | 2 + 8 files changed, 332 insertions(+), 107 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 7942cffe57..2041c04876 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -227,7 +227,8 @@ function initOptions(term: TerminalType): void { fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], - wordSeparator: null + wordSeparator: null, + allowedWindowOps: '' }; const options = Object.keys((term)._core.options); const booleanOptions = []; @@ -258,7 +259,7 @@ function initOptions(term: TerminalType): void { }); html += '
'; Object.keys(stringOptions).forEach(o => { - if (stringOptions[o]) { + if (stringOptions[o] && typeof stringOptions[o] !== 'string') { html += `
`; } else { html += `
`; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index d8defa1cc6..21d6d74273 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1266,4 +1266,151 @@ describe('InputHandler', () => { [131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072] ]); }); + describe('windowOps', () => { + it('all should be disabled by default and not report', () => { + const term = new TestTerminal({cols: 10, rows: 10}); + assert.deepEqual(term.options.allowedWindowOps, []); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b[14t'); + term.writeSync('\x1b[16t'); + term.writeSync('\x1b[18t'); + term.writeSync('\x1b[20t'); + term.writeSync('\x1b[21t'); + assert.deepEqual(stack, []); + }); + it('14 - GetWinSizePixels', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [14]}); + assert.deepEqual(term.options.allowedWindowOps, [14]); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b[14t'); + // does not report in test terminal due to missing renderer + assert.deepEqual(stack, []); + }); + it('16 - GetCellSizePixels', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [16]}); + assert.deepEqual(term.options.allowedWindowOps, [16]); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b[16t'); + // does not report in test terminal due to missing renderer + assert.deepEqual(stack, []); + }); + it('18 - GetWinSizeChars', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [18]}); + assert.deepEqual(term.options.allowedWindowOps, [18]); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b[18t'); + assert.deepEqual(stack, ['\x1b[8;10;10t']); + term.resize(50, 20); + term.writeSync('\x1b[18t'); + assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); + }); + it('20 - GetIconTitle', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [20]}); + assert.deepEqual(term.options.allowedWindowOps, [20]); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b]1;hello world!\x07'); + term.writeSync('\x1b[20t'); + assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\']); + term.writeSync('\x1b]1;some other\x07'); + term.writeSync('\x1b[20t'); + assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\', '\x1b]Lsome other\x1b\\']); + }); + it('21 - GetWinTitle', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [21]}); + assert.deepEqual(term.options.allowedWindowOps, [21]); + const stack: string[] = []; + term.onData(data => stack.push(data)); + term.writeSync('\x1b]2;hello world!\x07'); + term.writeSync('\x1b[21t'); + assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\']); + term.writeSync('\x1b]2;some other\x07'); + term.writeSync('\x1b[21t'); + assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\', '\x1b]lsome other\x1b\\']); + }); + it('22/23 - PushTitle/PopTitle', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); + assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const stack: string[] = []; + term.onTitleChange(data => stack.push(data)); + term.writeSync('\x1b]0;1\x07'); + term.writeSync('\x1b[22t'); + term.writeSync('\x1b]0;2\x07'); + term.writeSync('\x1b[22t'); + term.writeSync('\x1b]0;3\x07'); + term.writeSync('\x1b[22t'); + assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']); + assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']); + assert.deepEqual(stack, ['1', '2', '3']); + term.writeSync('\x1b[23t'); + term.writeSync('\x1b[23t'); + term.writeSync('\x1b[23t'); + term.writeSync('\x1b[23t'); // one more to test "overflow" + assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); + assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); + }); + it('22/23 - PushTitle/PopTitle with ;1', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); + assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const stack: string[] = []; + term.onTitleChange(data => stack.push(data)); + term.writeSync('\x1b]0;1\x07'); + term.writeSync('\x1b[22;1t'); + term.writeSync('\x1b]0;2\x07'); + term.writeSync('\x1b[22;1t'); + term.writeSync('\x1b]0;3\x07'); + term.writeSync('\x1b[22;1t'); + assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); + assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']); + assert.deepEqual(stack, ['1', '2', '3']); + term.writeSync('\x1b[23;1t'); + term.writeSync('\x1b[23;1t'); + term.writeSync('\x1b[23;1t'); + term.writeSync('\x1b[23;1t'); // one more to test "overflow" + assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); + assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3']); + }); + it('22/23 - PushTitle/PopTitle with ;2', () => { + const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); + assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const stack: string[] = []; + term.onTitleChange(data => stack.push(data)); + term.writeSync('\x1b]0;1\x07'); + term.writeSync('\x1b[22;2t'); + term.writeSync('\x1b]0;2\x07'); + term.writeSync('\x1b[22;2t'); + term.writeSync('\x1b]0;3\x07'); + term.writeSync('\x1b[22;2t'); + assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']); + assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3']); + term.writeSync('\x1b[23;2t'); + term.writeSync('\x1b[23;2t'); + term.writeSync('\x1b[23;2t'); + term.writeSync('\x1b[23;2t'); // one more to test "overflow" + assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); + assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); + }); + it('DECCOLM - should only work with "SetWinLines" (24) enabled', () => { + // disabled + const term = new TestTerminal({cols: 10, rows: 10}); + term.writeSync('\x1b[?3l'); + assert.equal((term as any)._bufferService.cols, 10); + term.writeSync('\x1b[?3h'); + assert.equal((term as any)._bufferService.cols, 10); + // enabled + const term2 = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [24]}); + term2.writeSync('\x1b[?3l'); + assert.equal((term2 as any)._bufferService.cols, 80); + term2.writeSync('\x1b[?3h'); + assert.equal((term2 as any)._bufferService.cols, 132); + }); + }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index de28c40cbe..574d29b8dc 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -33,6 +33,11 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, */ const MAX_PARSEBUFFER_LENGTH = 131072; +/** + * Limit length of title and icon name stacks. + */ +const STACK_LIMIT = 10; + /** * DCS subparser implementations @@ -127,6 +132,10 @@ export class InputHandler extends Disposable implements IInputHandler { private _stringDecoder: StringToUtf32 = new StringToUtf32(); private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32(); private _workCell: CellData = new CellData(); + private _windowTitle = ''; + private _iconName = ''; + private _windowTitleStack: string[] = []; + private _iconNameStack: string[] = []; private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } @@ -222,7 +231,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params)); this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params)); this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params)); - this._parser.setCsiHandler({final: 't'}, params => this.manipulateWindowOptions(params)); + this._parser.setCsiHandler({final: 't'}, params => this.windowOptions(params)); this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params)); this._parser.setCsiHandler({intermediates: '\'', final: '}'}, params => this.insertColumns(params)); this._parser.setCsiHandler({intermediates: '\'', final: '~'}, params => this.deleteColumns(params)); @@ -249,8 +258,9 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC handler */ // 0 - icon name + title - this._parser.setOscHandler(0, new OscHandler((data: string) => this.setTitle(data))); + this._parser.setOscHandler(0, new OscHandler((data: string) => { this.setTitle(data); this.setIconName(data); })); // 1 - icon name + this._parser.setOscHandler(1, new OscHandler((data: string) => this.setIconName(data))); // 2 - title this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); // 3 - set property X in the form "prop=value" @@ -502,6 +512,15 @@ export class InputHandler extends Disposable implements IInputHandler { * Forward addCsiHandler from parser. */ public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable { + if (id.final === 't' && !id.prefix && !id.intermediates) { + // security: always check whether window option is allowed + return this._parser.addCsiHandler(id, params => { + if (!~this._optionsService.options.allowedWindowOps.indexOf(params.params[0])) { + return true; + } + return callback(params); + }); + } return this._parser.addCsiHandler(id, callback); } @@ -1391,11 +1410,16 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.setgCharset(3, DEFAULT_CHARSET); // set VT100 mode here break; - case 3: // 132 col mode - // TODO: move DECCOLM into compat addon - this._terminal.savedCols = this._bufferService.cols; - this._terminal.resize(132, this._bufferService.rows); - this._terminal.reset(); + case 3: + /** + * DECCOLM - 132 column mode. + * This is only active if 'SetWinLines' (24) is listed + * in `options.allowedWindowOps`. + */ + if (~this._optionsService.options.allowedWindowOps.indexOf(24)) { + this._terminal.resize(132, this._bufferService.rows); + this._terminal.reset(); + } break; case 6: this._terminal.originMode = true; @@ -1574,11 +1598,15 @@ export class InputHandler extends Disposable implements IInputHandler { // TODO: move DECCOLM into compat addon // Note: This impl currently does not enforce col 80, instead reverts // to previous terminal width before entering DECCOLM 132 - if (this._bufferService.cols === 132 && this._terminal.savedCols) { - this._terminal.resize(this._terminal.savedCols, this._bufferService.rows); + /** + * DECCOLM - 80 column mode. + * This is only active if 'SetWinLines' (24) is listed + * in `options.allowedWindowOps`. + */ + if (~this._optionsService.options.allowedWindowOps.indexOf(24)) { + this._terminal.resize(80, this._bufferService.rows); + this._terminal.reset(); } - delete this._terminal.savedCols; - this._terminal.reset(); break; case 6: this._terminal.originMode = false; @@ -2020,96 +2048,91 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps ; Ps ; Ps t - Various window manipulations and reports (xterm) - * Ps = 1 -> De-iconify window. not supported - * Ps = 2 -> Iconify window. not supported - * Ps = 3 ; x ; y -> Move window to [x, y]. not supported - * Ps = 4 ; height ; width TBD - * Resize the xterm window to given height and width in pixels. - * Omitted parameters reuse the current height or width. - * Zero parameters use the display's height or width. - * Ps = 5 not supported - * Raise the xterm window to the front of the stacking order. - * Ps = 6 not supported - * Lower the xterm window to the bottom of the stacking order. - * Ps = 7 TBD - * Refresh the xterm window. - * Ps = 8 ; height ; width TBD - * Resize the text area to given height and width in characters. - * Omitted parameters reuse the current height or width. - * Zero parameters use the display's height or width. - * Ps = 9 ; 0 -> Restore maximized window. TBD - * Ps = 9 ; 1 TBD - * Maximize window (i.e., resize to screen size). - * Ps = 9 ; 2 -> Maximize window vertically. TBD - * Ps = 9 ; 3 -> Maximize window horizontally. TBD - * Ps = 10 ; 0 -> Undo full-screen mode. TBD - * Ps = 10 ; 1 -> Change to full-screen. TBD - * Ps = 10 ; 2 -> Toggle full-screen. TBD - * Ps = 11 not supported (always report non-iconified?) - * Report xterm window state. - * If the xterm window is non-iconified, it returns CSI 1 t . - * If the xterm window is iconified, it returns CSI 2 t . - * Ps = 13 -> Report xterm window position. TBD - * Note: X Toolkit positions can be negative, but the reported - * values are unsigned, in the range 0-65535. Negative values - * correspond to 32768-65535. - * Result is CSI 3 ; x ; y t - * Ps = 13 ; 2 TBD - * Report xterm text-area position. Result is CSI 3 ; x ; y t - * Ps = 1 4 TBD - * Report xterm text area size in pixels. Result is CSI 4 ; height ; width t - * Ps = 14 ; 2 TBD - * Report xterm window size in pixels. - * Normally xterm's window is larger than its text area, since it - * includes the frame (or decoration) applied by the window manager, - * as well as the area used by a scroll-bar. Result is CSI 4 ; height ; width t - * Ps = 15 TBD - * Report size of the screen in pixels. Result is CSI 5 ; height ; width t - * Ps = 16 TBD - * Report xterm character cell size in pixels. Result is CSI 6 ; height ; width t - * Ps = 18 TBD - * Report the size of the text area in characters. Result is CSI 8 ; height ; width t - * Ps = 19 TBD - * Report the size of the screen in characters. Result is CSI 9 ; height ; width t - * Ps = 20 TBD - * Report xterm window's icon label. Result is OSC L label ST - * Ps = 21 TBD - * Report xterm window's title. Result is OSC l label ST - * Ps = 22 ; 0 -> Save xterm icon and window title on stack. TBD - * Ps = 22 ; 1 -> Save xterm icon title on stack. TBD - * Ps = 22 ; 2 -> Save xterm window title on stack. TBD - * Ps = 23 ; 0 -> Restore xterm icon and window title from stack. TBD - * Ps = 23 ; 1 -> Restore xterm icon title from stack. TBD - * Ps = 23 ; 2 -> Restore xterm window title from stack. TBD - * Ps >= 24 TBD - * Resize to Ps lines (DECSLPP), VT340 and VT420. xterm adapts this by resizing its window. - */ - public manipulateWindowOptions(params?: IParams): void { - console.log(params); + * + * Note: Only those listed below are supported. All others are left to integrators and + * need special treatment based on the embedding environment. + * + * Ps = 1 4 supported + * Report xterm text area size in pixels. + * Result is CSI 4 ; height ; width t + * Ps = 14 ; 2 not implemented + * Ps = 16 supported + * Report xterm character cell size in pixels. + * Result is CSI 6 ; height ; width t + * Ps = 18 supported + * Report the size of the text area in characters. + * Result is CSI 8 ; height ; width t + * Ps = 20 supported + * Report xterm window's icon label. + * Result is OSC L label ST + * Ps = 21 supported + * Report xterm window's title. + * Result is OSC l label ST + * Ps = 22 ; 0 -> Save xterm icon and window title on stack. supported + * Ps = 22 ; 1 -> Save xterm icon title on stack. supported + * Ps = 22 ; 2 -> Save xterm window title on stack. supported + * Ps = 23 ; 0 -> Restore xterm icon and window title from stack. supported + * Ps = 23 ; 1 -> Restore xterm icon title from stack. supported + * Ps = 23 ; 2 -> Restore xterm window title from stack. supported + * Ps >= 24 not implemented + */ + public windowOptions(params?: IParams): void { + if (!~this._optionsService.options.allowedWindowOps.indexOf(params.params[0])) { + return; + } + const second = (params.length > 1) ? params.params[1] : 0; + const rs = (this._terminal as any)._renderService; // FIXME: import renderService to get rid of any type here switch (params.params[0]) { - case 8: - if (params.length === 3) { - // TODO: 0/1 support - const cols = Math.max(2, params.params[2]); - const rows = Math.max(2, params.params[1]); - // make it work with demo thus shamelessly taken from demo's client.ts - const width = (cols * (this._terminal as any)._renderService.dimensions.actualCellWidth + this._terminal.viewport.scrollBarWidth).toString() + 'px'; - const height = (rows * (this._terminal as any)._renderService.dimensions.actualCellHeight).toString() + 'px'; - (this._terminal as any)._parent.style.width = width; - (this._terminal as any)._parent.style.height = height; - const term = Function('return this.term;')(); - // move fit into browser codebase? - for (const addon of (term as any)._addonManager._addons) { - if (`${addon.instance.constructor}`.indexOf('FitAddon') !== -1) { - addon.instance.fit(); - } + case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t + if (rs && second !== 2) { + const w = rs.dimensions.canvasWidth.toFixed(0); + const h = rs.dimensions.canvasHeight.toFixed(0); + this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); + } + break; + case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t + if (rs) { + const w = rs.dimensions.actualCellWidth.toFixed(0); + const h = rs.dimensions.actualCellHeight.toFixed(0); + this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); + } + break; + case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t + if (this._bufferService) { + this._coreService.triggerDataEvent(`${C0.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`); + } + break; + case 20: // GetIconTitle, returns OSC L label ST + this._coreService.triggerDataEvent(`${C0.ESC}]L${this._iconName}${C0.ESC}\\`); + break; + case 21: // GetWinTitle, returns OSC l label ST + this._coreService.triggerDataEvent(`${C0.ESC}]l${this._windowTitle}${C0.ESC}\\`); + break; + case 22: // PushTitle + if (!second || second === 2) { + this._windowTitleStack.push(this._windowTitle); + if (this._windowTitleStack.length > STACK_LIMIT) { + this._windowTitleStack.shift(); + } + } + if (!second || second === 1) { + this._iconNameStack.push(this._iconName); + if (this._iconNameStack.length > STACK_LIMIT) { + this._iconNameStack.shift(); } } break; - case 18: - const height = this._bufferService.rows; - const width = this._bufferService.cols; - this._coreService.triggerDataEvent(`${C0.ESC}[8;${height};${width}t`); + case 23: // PopTitle + if (!second || second === 2) { + if (this._windowTitleStack.length) { + this.setTitle(this._windowTitleStack.pop()); + } + } + if (!second || second === 1) { + if (this._iconNameStack.length) { + this.setIconName(this._iconNameStack.pop()); + } + } break; } } @@ -2148,14 +2171,22 @@ export class InputHandler extends Disposable implements IInputHandler { /** - * OSC 0; ST (set icon name + window title) * OSC 2; ST (set window title) - * Proxy to set window title. Icon name is not supported. + * Proxy to set window title. */ public setTitle(data: string): void { + this._windowTitle = data; this._terminal.handleTitle(data); } + /** + * OSC 1; ST + * Note: Icon name is not exposed. + */ + public setIconName(data: string): void { + this._iconName = data; + } + /** * ESC E * C1.NEL diff --git a/src/Terminal.ts b/src/Terminal.ts index 3e7e55ca99..ffa34da807 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -131,8 +131,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public sendFocus: boolean; // misc - public savedCols: number; - public curAttrData: IAttributeData; private _eraseAttrData: IAttributeData; @@ -1463,7 +1461,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an // invalid location - this.viewport.syncScrollArea(true); + if (this.viewport) { + this.viewport.syncScrollArea(true); + } this.refresh(0, this.rows - 1); this._onResize.fire({ cols: x, rows: y }); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index a70f751c59..640a6c7606 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -211,7 +211,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { wraparoundMode: boolean; bracketedPasteMode: boolean; curAttrData = new AttributeData(); - savedCols: number; x10Mouse: boolean; vt200Mouse: boolean; normalMouse: boolean; diff --git a/src/Types.d.ts b/src/Types.d.ts index be1019e58c..faccb40257 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -35,7 +35,6 @@ export interface IInputHandlingTerminal { wraparoundMode: boolean; bracketedPasteMode: boolean; curAttrData: IAttributeData; - savedCols: number; mouseEvents: CoreMouseEventType; sendFocus: boolean; cursorHidden: boolean; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d9ea60d5a8..3c3d4bea52 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -14,6 +14,34 @@ import { clone } from 'common/Clone'; // made, apart from the conversion to base64. export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; +// setting names controlled by allowedWindowOps +// not supported: GetChecksum, GetSelection, SetChecksum, SetSelection, SetXprop +export const WINDOW_OPTIONS: {[key: string]: number} = { + 'RestoreWin': 1, + 'MinimizeWin': 2, + 'SetWinPosition': 3, + 'SetWinSizePixels': 4, + 'RaiseWin': 5, + 'LowerWin': 6, + 'RefreshWin': 7, + 'SetWinSizeChars': 8, + 'MaximizeWin': 9, + 'FullscreenWin': 10, + 'GetWinState': 11, + 'GetWinPosition': 13, + 'GetWinSizePixels': 14, + 'GetScreenSizePixels': 15, // note: name not in xterm + 'GetCellSizePixels': 16, // note: name not in xterm + 'GetWinSizeChars': 18, + 'GetScreenSizeChars': 19, + 'GetIconTitle': 20, + 'GetWinTitle': 21, + 'PushTitle': 22, + 'PopTitle': 23, + 'SetWinLines': 24 // any param >= 24, also handles DECCOLM +}; + + // TODO: Freeze? export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ cols: 80, @@ -50,7 +78,8 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenKeys: false, cancelEvents: false, useFlowControl: false, - wordSeparator: ' ()[]{}\',:;"' + wordSeparator: ' ()[]{}\',:;"', + allowedWindowOps: [] }); /** @@ -128,6 +157,20 @@ export class OptionsService implements IOptionsService { throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`); } break; + case 'allowedWindowOps': + const values = (value as string).split(','); + const cleaned: number[] = []; + for (let i = 0; i < values.length; ++i) { + const option = parseInt(values[i].trim()) || WINDOW_OPTIONS[values[i].trim()]; + if (!option || option < 0 || option > 24) { + throw new Error(`unknown window option "${values[i]}"`); + } + if (!~cleaned.indexOf(option)) { + cleaned.push(option); + } + } + value = cleaned; + break; } return value; } @@ -136,6 +179,9 @@ export class OptionsService implements IOptionsService { if (!(key in DEFAULT_OPTIONS)) { throw new Error(`No option with key "${key}"`); } + if (key === 'allowedWindowOps') { + return this.options[key].join(); + } return this.options[key]; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 0872d3db7b..a0a25bb81d 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -201,6 +201,7 @@ export interface IPartialTerminalOptions { theme?: ITheme; windowsMode?: boolean; wordSeparator?: string; + allowedWindowOps?: number[]; } export interface ITerminalOptions { @@ -240,6 +241,7 @@ export interface ITerminalOptions { screenKeys: boolean; termName: string; useFlowControl: boolean; + allowedWindowOps: number[]; } export interface ITheme { From 2cad586ccc1d1fb6f4a875427692a967142b0463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 29 Oct 2019 15:50:54 +0100 Subject: [PATCH 03/15] add integration tests for pixel reports --- src/InputHandler.ts | 2 +- test/api/InputHandler.api.ts | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 574d29b8dc..259a2b1c15 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -2094,7 +2094,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (rs) { const w = rs.dimensions.actualCellWidth.toFixed(0); const h = rs.dimensions.actualCellHeight.toFixed(0); - this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); + this._coreService.triggerDataEvent(`${C0.ESC}[6;${h};${w}t`); } break; case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index fe06861034..991c5cb5d8 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -346,6 +346,51 @@ describe('InputHandler Integration Tests', function(): void { `); assert.deepEqual(await getLinesAsArray(3), ['#', ' #', 'abcd####']); }); + + describe.only('Window Options - CSI Ps ; Ps ; Ps t', () => { + it('should be disabled by default', async function() { + assert.equal(await page.evaluate(`(() => window.term.getOption('allowedWindowOps'))()`), ''); + await page.evaluate(`(() => { + window._stack = []; + const _h = window.term.onData(data => window._stack.push(data)); + window.term.write('\x1b[14t'); + window.term.write('\x1b[16t'); + window.term.write('\x1b[18t'); + window.term.write('\x1b[20t'); + window.term.write('\x1b[21t'); + return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); + })()`); + assert.deepEqual(await page.evaluate(`(() => _stack)()`), []); + }); + it('14 - GetWinSizePixels', async function() { + assert.equal(await page.evaluate(`(() => { + window.term.setOption('allowedWindowOps', 'GetWinSizePixels'); + return window.term.getOption('allowedWindowOps'); + })()`), '14'); + await page.evaluate(`(() => { + window._stack = []; + const _h = window.term.onData(data => window._stack.push(data)); + window.term.write('\x1b[14t'); + return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); + })()`); + const d = await getDimensions(); + assert.deepEqual(await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]); + }); + it('16 - GetCellSizePixels', async function() { + assert.equal(await page.evaluate(`(() => { + window.term.setOption('allowedWindowOps', 'GetCellSizePixels'); + return window.term.getOption('allowedWindowOps'); + })()`), '16'); + await page.evaluate(`(() => { + window._stack = []; + const _h = window.term.onData(data => window._stack.push(data)); + window.term.write('\x1b[16t'); + return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); + })()`); + const d = await getDimensions(); + assert.deepEqual(await page.evaluate(`(() => _stack)()`), [`\x1b[6;${d.cellHeight};${d.cellWidth}t`]); + }); + }); }); describe('ESC', () => { @@ -404,3 +449,17 @@ async function getCursor(): Promise<{col: number, row: number}> { })(); `); } + +async function getDimensions(): Promise { + const dim = await page.evaluate(` + (function() { + return term._core._renderService.dimensions; + })(); + `); + return { + cellWidth: dim.actualCellWidth.toFixed(0), + cellHeight: dim.actualCellHeight.toFixed(0), + width: dim.canvasWidth.toFixed(0), + height: dim.canvasHeight.toFixed(0) + }; +} From 98cc17df24a9c4e06704abe25373b315c14c23d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 29 Oct 2019 15:57:11 +0100 Subject: [PATCH 04/15] remove leftover .only --- test/api/InputHandler.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 991c5cb5d8..a3725f9c84 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -347,7 +347,7 @@ describe('InputHandler Integration Tests', function(): void { assert.deepEqual(await getLinesAsArray(3), ['#', ' #', 'abcd####']); }); - describe.only('Window Options - CSI Ps ; Ps ; Ps t', () => { + describe('Window Options - CSI Ps ; Ps ; Ps t', () => { it('should be disabled by default', async function() { assert.equal(await page.evaluate(`(() => window.term.getOption('allowedWindowOps'))()`), ''); await page.evaluate(`(() => { From d09e2fbcec60e0c4fe0edfbed5b71163f57ee46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 13 Nov 2019 23:09:52 +0100 Subject: [PATCH 05/15] stub for window options --- src/common/parser/WindowOptions.ts | 137 +++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/common/parser/WindowOptions.ts diff --git a/src/common/parser/WindowOptions.ts b/src/common/parser/WindowOptions.ts new file mode 100644 index 0000000000..a75faa6302 --- /dev/null +++ b/src/common/parser/WindowOptions.ts @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +interface IWindowOptions { + [key: string]: boolean | undefined; + restoreWin?: boolean; + minimizeWin?: boolean; + setWinPosition?: boolean; + setWinSizePixels?: boolean; + raiseWin?: boolean; + lowerWin?: boolean; + refreshWin?: boolean; + setWinSizeChars?: boolean; + maximizeWin?: boolean; + fullscreenWin?: boolean; + getWinState?: boolean; + getWinPosition?: boolean; + getWinSizePixels?: boolean; + getScreenSizePixels?: boolean; + getCellSizePixels?: boolean; + getWinSizeChars?: boolean; + getScreenSizeChars?: boolean; + getIconTitle?: boolean; + getWinTitle?: boolean; + pushTitle?: boolean; + popTitle?: boolean; + setWinLines?: boolean; +} + +const enum WindowOptions { + restoreWin = 1 << 1, + minimizeWin = 1 << 2, + setWinPosition = 1 << 3, + setWinSizePixels = 1 << 4, + raiseWin = 1 << 5, + lowerWin = 1 << 6, + refreshWin = 1 << 7, + setWinSizeChars = 1 << 8, + maximizeWin = 1 << 9, + fullscreenWin = 1 << 10, + getWinState = 1 << 11, + getWinPosition = 1 << 13, + getWinSizePixels = 1 << 14, + getScreenSizePixels = 1 << 15, // note: name not in xterm + getCellSizePixels = 1 << 16, // note: name not in xterm + getWinSizeChars = 1 << 18, + getScreenSizeChars = 1 << 19, + getIconTitle = 1 << 20, + getWinTitle = 1 << 21, + pushTitle = 1 << 22, + popTitle = 1 << 23, + setWinLines = 1 << 24 // any param >= 24, also handles DECCOLM +} + +let layer: {[key in keyof typeof WindowOptions]?: boolean} + +function hasOption(n: number, opts: WindowOptions): boolean { + return !!(opts & (1 << Math.max(n, 24))); +} + +function getOptions(opts: WindowOptions): IWindowOptions { + /* + const result: IWindowOptions = {}; + for (const key in WindowOptions) { + if (parseInt(key)) continue; + result[key] = !!(opts & WindowOptions[key as any] as unknown as number); + } + return result; + */ + return { + restoreWin: !!(opts & WindowOptions.restoreWin), + minimizeWin: !!(opts & WindowOptions.minimizeWin), + setWinPosition: !!(opts & WindowOptions.setWinPosition), + setWinSizePixels: !!(opts & WindowOptions.setWinSizePixels), + raiseWin: !!(opts & WindowOptions.raiseWin), + lowerWin: !!(opts & WindowOptions.lowerWin), + refreshWin: !!(opts & WindowOptions.refreshWin), + setWinSizeChars: !!(opts & WindowOptions.setWinSizeChars), + maximizeWin: !!(opts & WindowOptions.maximizeWin), + fullscreenWin: !!(opts & WindowOptions.fullscreenWin), + getWinState: !!(opts & WindowOptions.getWinState), + getWinPosition: !!(opts & WindowOptions.getWinPosition), + getWinSizePixels: !!(opts & WindowOptions.getWinSizePixels), + getScreenSizePixels: !!(opts & WindowOptions.getScreenSizePixels), + getCellSizePixels: !!(opts & WindowOptions.getCellSizePixels), + getWinSizeChars: !!(opts & WindowOptions.getWinSizeChars), + getScreenSizeChars: !!(opts & WindowOptions.getScreenSizeChars), + getIconTitle: !!(opts & WindowOptions.getIconTitle), + getWinTitle: !!(opts & WindowOptions.getWinTitle), + pushTitle: !!(opts & WindowOptions.pushTitle), + popTitle: !!(opts & WindowOptions.popTitle), + setWinLines: !!(opts & WindowOptions.setWinLines) + }; +} + +function setOptions(v: IWindowOptions & {[key: string]: boolean}, opts: WindowOptions): WindowOptions { + for (const optionName in v) { + const value = v[optionName]; + //const option = WindowOptions[optionName as any] as unknown as number; + //opts = value ? opts | option : opts & ~option; + switch (optionName) { + case 'restoreWin': opts = value ? opts | WindowOptions.restoreWin : opts & ~WindowOptions.restoreWin; break; + case 'minimizeWin': opts = value ? opts | WindowOptions.minimizeWin : opts & ~WindowOptions.minimizeWin; break; + case 'setWinPosition': opts = value ? opts | WindowOptions.setWinPosition : opts & ~WindowOptions.setWinPosition; break; + case 'setWinSizePixels': opts = value ? opts | WindowOptions.setWinSizePixels : opts & ~WindowOptions.setWinSizePixels; break; + case 'raiseWin': opts = value ? opts | WindowOptions.raiseWin : opts & ~WindowOptions.raiseWin; break; + case 'lowerWin': opts = value ? opts | WindowOptions.lowerWin : opts & ~WindowOptions.lowerWin; break; + case 'refreshWin': opts = value ? opts | WindowOptions.refreshWin : opts & ~WindowOptions.refreshWin; break; + case 'setWinSizeChars': opts = value ? opts | WindowOptions.setWinSizeChars : opts & ~WindowOptions.setWinSizeChars; break; + case 'maximizeWin': opts = value ? opts | WindowOptions.maximizeWin : opts & ~WindowOptions.maximizeWin; break; + case 'fullscreenWin': opts = value ? opts | WindowOptions.fullscreenWin : opts & ~WindowOptions.fullscreenWin; break; + case 'getWinState': opts = value ? opts | WindowOptions.getWinState : opts & ~WindowOptions.getWinState; break; + case 'getWinPosition': opts = value ? opts | WindowOptions.getWinPosition : opts & ~WindowOptions.getWinPosition; break; + case 'getWinSizePixels': opts = value ? opts | WindowOptions.getWinSizePixels : opts & ~WindowOptions.getWinSizePixels; break; + case 'getScreenSizePixels': opts = value ? opts | WindowOptions.getScreenSizePixels : opts & ~WindowOptions.getScreenSizePixels; break; + case 'getCellSizePixels': opts = value ? opts | WindowOptions.getCellSizePixels : opts & ~WindowOptions.getCellSizePixels; break; + case 'getWinSizeChars': opts = value ? opts | WindowOptions.getWinSizeChars : opts & ~WindowOptions.getWinSizeChars; break; + case 'getScreenSizeChars': opts = value ? opts | WindowOptions.getScreenSizeChars : opts & ~WindowOptions.getScreenSizeChars; break; + case 'getIconTitle': opts = value ? opts | WindowOptions.getIconTitle : opts & ~WindowOptions.getIconTitle; break; + case 'getWinTitle': opts = value ? opts | WindowOptions.getWinTitle : opts & ~WindowOptions.getWinTitle; break; + case 'pushTitle': opts = value ? opts | WindowOptions.pushTitle : opts & ~WindowOptions.pushTitle; break; + case 'popTitle': opts = value ? opts | WindowOptions.popTitle : opts & ~WindowOptions.popTitle; break; + case 'setWinLines': opts = value ? opts | WindowOptions.setWinLines : opts & ~WindowOptions.setWinLines; break; + default: throw new Error(`unknown WindowOption "${optionName}"`); + } + } + return opts; +} + +declare const console: any; +let opts: WindowOptions = 0; + +opts = setOptions({refreshWin: true, pushTitle: true, popTitle: true}, opts); +console.log(opts); +console.log(getOptions(opts)); From 8fc2d22b2870c33f899d1a73eea17c5a77027474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 10:20:51 +0100 Subject: [PATCH 06/15] add IWindowOptions to xterm.d.ts --- typings/xterm.d.ts | 127 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9728a72cb5..3433bd1e61 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -362,6 +362,133 @@ declare module 'xterm' { tooMuchOutput: string; } + /** + * Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t). + * + * Note that most settings have no default implementation, as they heavily + * rely on the embedding environment. + * + * To implement a features, create a custom CSI hook like this: + * ```Typescript + * term.parser.addCsiHandler({final: 't'}, params => { + * const ps = params[0]; + * switch (ps) { + * case XY: + * ... // your implementation + * return true; // signal Ps=XY was handled + * } + * return false; // any Ps that was not handled + * }); + * ``` + * + * All features are disabled by default for security reasons. + * All custom implementations are guarded by the boolean values automatically. + */ + export interface IWindowOptions { + /** Ps=1 De-iconify window. */ + restoreWin?: boolean; + /** Ps=2 Iconify window. */ + minimizeWin?: boolean; + /** + * Ps=3 ; x ; y + * Move window to [x, y]. + */ + setWinPosition?: boolean; + /** + * Ps = 4 ; height ; width + * Resize the window to given `height` and `width` in pixels. + * Omitted parameters should reuse the current height or width. + * Zero parameters should use the display's height or width. + */ + setWinSizePixels?: boolean; + /** Ps=5 Raise the window to the front of the stacking order. */ + raiseWin?: boolean; + /** Ps=6 Lower the xterm window to the bottom of the stacking order. */ + lowerWin?: boolean; + /** Ps=7 Refresh the window. */ + refreshWin?: boolean; + /** + * Ps = 8 ; height ; width + * Resize the text area to given height and width in characters. + * Omitted parameters should reuse the current height or width. + * Zero parameters use the display's height or width. + */ + setWinSizeChars?: boolean; + /** + * Ps=9 ; 0 Restore maximized window. + * Ps=9 ; 1 Maximize window (i.e., resize to screen size). + * Ps=9 ; 2 Maximize window vertically. + * Ps=9 ; 3 Maximize window horizontally. + */ + maximizeWin?: boolean; + /** + * Ps=10 ; 0 Undo full-screen mode. + * Ps=10 ; 1 Change to full-screen. + * Ps=10 ; 2 Toggle full-screen. + */ + fullscreenWin?: boolean; + /** Ps=11 Report xterm window state. + * If the xterm window is non-iconified, it returns "CSI 1 t". + * If the xterm window is iconified, it returns "CSI 2 t". + */ + getWinState?: boolean; + /** + * Ps=13 Report xterm window position. Result is "CSI 3 ; x ; y t". + * Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t". + */ + getWinPosition?: boolean; + /** + * Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t". + * Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t". + * Ps=14 has a default implementation. + */ + getWinSizePixels?: boolean; + /** Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t". */ + getScreenSizePixels?: boolean; + /** + * Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t". + * Implemented by default. + */ + getCellSizePixels?: boolean; + /** + * Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t". + * Implemented by default. + */ + getWinSizeChars?: boolean; + /** Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t". */ + getScreenSizeChars?: boolean; + /** + * Ps=20 Report xterm window's icon label. Result is "OSC L label ST". + * Implemented by default. + */ + getIconTitle?: boolean; + /** + * Ps=21 Report xterm window's title. Result is "OSC l label ST". + * Implemented by default. + */ + getWinTitle?: boolean; + /** + * Ps=22 ; 0 Save xterm icon and window title on stack. + * Ps=22 ; 1 Save xterm icon title on stack. + * Ps=22 ; 2 Save xterm window title on stack. + * All variants have a default implementation. + */ + pushTitle?: boolean; + /** + * Ps=23 ; 0 Restore xterm icon and window title from stack. + * Ps=23 ; 1 Restore xterm icon title from stack. + * Ps=23 ; 2 Restore xterm window title from stack. + * All variants have a default implementation. + */ + popTitle?: boolean; + /** + * Ps>=24 Resize to Ps lines (DECSLPP). + * DECSLPP is not implemented. This settings is also used to + * enable / disable DECCOLM (earlier variant of DECSLPP). + */ + setWinLines?: boolean; + } + /** * The class that represents an xterm.js terminal. */ From eeb9270882102ed46969db49ccc40b2116ba6aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 11:23:21 +0100 Subject: [PATCH 07/15] change WindowOptions type --- demo/client.ts | 6 +-- src/InputHandler.test.ts | 39 +++++++++-------- src/InputHandler.ts | 24 +++++----- src/common/Types.d.ts | 29 ++++++++++++ src/common/{parser => }/WindowOptions.ts | 56 +++--------------------- src/common/services/OptionsService.ts | 50 +++------------------ src/common/services/Services.ts | 6 ++- typings/xterm.d.ts | 2 +- 8 files changed, 82 insertions(+), 130 deletions(-) rename src/common/{parser => }/WindowOptions.ts (76%) diff --git a/demo/client.ts b/demo/client.ts index 4eb0c2acf9..42e4fe7d13 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -244,7 +244,8 @@ function initOptions(term: TerminalType): void { 'termName', 'useFlowControl', // Complex option - 'theme' + 'theme', + 'windowOptions' ]; const stringOptions = { bellSound: null, @@ -256,8 +257,7 @@ function initOptions(term: TerminalType): void { fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], - wordSeparator: null, - allowedWindowOps: '' + wordSeparator: null }; const options = Object.keys((term)._core.options); const booleanOptions = []; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 21d6d74273..afbef1682e 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -17,6 +17,7 @@ import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsSer import { IBufferService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; +import { WindowOptions } from 'common/WindowOptions'; function getCursor(term: TestTerminal): number[] { return [ @@ -1266,10 +1267,10 @@ describe('InputHandler', () => { [131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072] ]); }); - describe('windowOps', () => { + describe('windowOptions', () => { it('all should be disabled by default and not report', () => { const term = new TestTerminal({cols: 10, rows: 10}); - assert.deepEqual(term.options.allowedWindowOps, []); + assert.deepEqual(term.options.windowOptions, 0); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[14t'); @@ -1280,8 +1281,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('14 - GetWinSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [14]}); - assert.deepEqual(term.options.allowedWindowOps, [14]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinSizePixels}); + assert.deepEqual(term.options.windowOptions, 1 << 14); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[14t'); @@ -1289,8 +1290,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('16 - GetCellSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [16]}); - assert.deepEqual(term.options.allowedWindowOps, [16]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getCellSizePixels}); + assert.deepEqual(term.options.windowOptions, 1 << 16); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[16t'); @@ -1298,8 +1299,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('18 - GetWinSizeChars', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [18]}); - assert.deepEqual(term.options.allowedWindowOps, [18]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinSizeChars}); + assert.deepEqual(term.options.windowOptions, 1 << 18); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[18t'); @@ -1309,8 +1310,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); }); it('20 - GetIconTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [20]}); - assert.deepEqual(term.options.allowedWindowOps, [20]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getIconTitle}); + assert.deepEqual(term.options.windowOptions, 1 << 20); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b]1;hello world!\x07'); @@ -1321,8 +1322,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\', '\x1b]Lsome other\x1b\\']); }); it('21 - GetWinTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [21]}); - assert.deepEqual(term.options.allowedWindowOps, [21]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinTitle}); + assert.deepEqual(term.options.windowOptions, 1 << 21); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b]2;hello world!\x07'); @@ -1333,8 +1334,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\', '\x1b]lsome other\x1b\\']); }); it('22/23 - PushTitle/PopTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); - assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); + assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1355,8 +1356,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); }); it('22/23 - PushTitle/PopTitle with ;1', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); - assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); + assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1377,8 +1378,8 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['1', '2', '3']); }); it('22/23 - PushTitle/PopTitle with ;2', () => { - const term = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [22, 23]}); - assert.deepEqual(term.options.allowedWindowOps, [22, 23]); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); + assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1406,7 +1407,7 @@ describe('InputHandler', () => { term.writeSync('\x1b[?3h'); assert.equal((term as any)._bufferService.cols, 10); // enabled - const term2 = new TestTerminal({cols: 10, rows: 10, allowedWindowOps: [24]}); + const term2 = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.setWinLines}); term2.writeSync('\x1b[?3l'); assert.equal((term2 as any)._bufferService.cols, 80); term2.writeSync('\x1b[?3h'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 8141389904..21bab83b28 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -22,6 +22,7 @@ import { IAttributeData, IDisposable } from 'common/Types'; import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; +import { hasWindowOption, WindowOptions } from 'common/WindowOptions'; /** * Map collect to glevel. Used in `selectCharset`. @@ -520,7 +521,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (id.final === 't' && !id.prefix && !id.intermediates) { // security: always check whether window option is allowed return this._parser.addCsiHandler(id, params => { - if (!~this._optionsService.options.allowedWindowOps.indexOf(params.params[0])) { + if (!hasWindowOption(params.params[0], this._optionsService.options.windowOptions)) { return true; } return callback(params); @@ -1419,9 +1420,9 @@ export class InputHandler extends Disposable implements IInputHandler { /** * DECCOLM - 132 column mode. * This is only active if 'SetWinLines' (24) is listed - * in `options.allowedWindowOps`. + * in `options.windowsOptions`. */ - if (~this._optionsService.options.allowedWindowOps.indexOf(24)) { + if (this._optionsService.options.windowOptions & WindowOptions.setWinLines) { this._terminal.resize(132, this._bufferService.rows); this._terminal.reset(); } @@ -1606,9 +1607,9 @@ export class InputHandler extends Disposable implements IInputHandler { /** * DECCOLM - 80 column mode. * This is only active if 'SetWinLines' (24) is listed - * in `options.allowedWindowOps`. + * in `options.windowsOptions`. */ - if (~this._optionsService.options.allowedWindowOps.indexOf(24)) { + if (this._optionsService.options.windowOptions & WindowOptions.setWinLines) { this._terminal.resize(80, this._bufferService.rows); this._terminal.reset(); } @@ -2081,8 +2082,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 23 ; 2 -> Restore xterm window title from stack. supported * Ps >= 24 not implemented */ - public windowOptions(params?: IParams): void { - if (!~this._optionsService.options.allowedWindowOps.indexOf(params.params[0])) { + public windowOptions(params: IParams): void { + if (!hasWindowOption(params.params[0], this._optionsService.options.windowOptions)) { return; } const second = (params.length > 1) ? params.params[1] : 0; @@ -2090,15 +2091,16 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t if (rs && second !== 2) { - const w = rs.dimensions.canvasWidth.toFixed(0); - const h = rs.dimensions.canvasHeight.toFixed(0); + console.log(rs.dimensions); + const w = rs.dimensions.scaledCanvasWidth.toFixed(0); + const h = rs.dimensions.scaledCanvasHeight.toFixed(0); this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); } break; case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t if (rs) { - const w = rs.dimensions.actualCellWidth.toFixed(0); - const h = rs.dimensions.actualCellHeight.toFixed(0); + const w = rs.dimensions.scaledCellWidth.toFixed(0); + const h = rs.dimensions.scaledCellHeight.toFixed(0); this._coreService.triggerDataEvent(`${C0.ESC}[6;${h};${w}t`); } break; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 2f74203899..9b38aa3c94 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -251,3 +251,32 @@ export interface ICoreMouseProtocol { * with the active encoding and sent out. */ export type CoreMouseEncoding = (event: ICoreMouseEvent) => string; + +/** + * WindowOption settings. + */ +export interface IWindowOptions { + [key: string]: boolean | undefined; + restoreWin?: boolean; + minimizeWin?: boolean; + setWinPosition?: boolean; + setWinSizePixels?: boolean; + raiseWin?: boolean; + lowerWin?: boolean; + refreshWin?: boolean; + setWinSizeChars?: boolean; + maximizeWin?: boolean; + fullscreenWin?: boolean; + getWinState?: boolean; + getWinPosition?: boolean; + getWinSizePixels?: boolean; + getScreenSizePixels?: boolean; + getCellSizePixels?: boolean; + getWinSizeChars?: boolean; + getScreenSizeChars?: boolean; + getIconTitle?: boolean; + getWinTitle?: boolean; + pushTitle?: boolean; + popTitle?: boolean; + setWinLines?: boolean; +} diff --git a/src/common/parser/WindowOptions.ts b/src/common/WindowOptions.ts similarity index 76% rename from src/common/parser/WindowOptions.ts rename to src/common/WindowOptions.ts index a75faa6302..582a521520 100644 --- a/src/common/parser/WindowOptions.ts +++ b/src/common/WindowOptions.ts @@ -2,34 +2,9 @@ * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ +import { IWindowOptions } from 'common/Types'; -interface IWindowOptions { - [key: string]: boolean | undefined; - restoreWin?: boolean; - minimizeWin?: boolean; - setWinPosition?: boolean; - setWinSizePixels?: boolean; - raiseWin?: boolean; - lowerWin?: boolean; - refreshWin?: boolean; - setWinSizeChars?: boolean; - maximizeWin?: boolean; - fullscreenWin?: boolean; - getWinState?: boolean; - getWinPosition?: boolean; - getWinSizePixels?: boolean; - getScreenSizePixels?: boolean; - getCellSizePixels?: boolean; - getWinSizeChars?: boolean; - getScreenSizeChars?: boolean; - getIconTitle?: boolean; - getWinTitle?: boolean; - pushTitle?: boolean; - popTitle?: boolean; - setWinLines?: boolean; -} - -const enum WindowOptions { +export const enum WindowOptions { restoreWin = 1 << 1, minimizeWin = 1 << 2, setWinPosition = 1 << 3, @@ -54,21 +29,11 @@ const enum WindowOptions { setWinLines = 1 << 24 // any param >= 24, also handles DECCOLM } -let layer: {[key in keyof typeof WindowOptions]?: boolean} - -function hasOption(n: number, opts: WindowOptions): boolean { - return !!(opts & (1 << Math.max(n, 24))); +export function hasWindowOption(n: number, opts: WindowOptions): boolean { + return !!(opts & (1 << Math.min(n, 24))); } -function getOptions(opts: WindowOptions): IWindowOptions { - /* - const result: IWindowOptions = {}; - for (const key in WindowOptions) { - if (parseInt(key)) continue; - result[key] = !!(opts & WindowOptions[key as any] as unknown as number); - } - return result; - */ +export function getWindowOptions(opts: WindowOptions): IWindowOptions { return { restoreWin: !!(opts & WindowOptions.restoreWin), minimizeWin: !!(opts & WindowOptions.minimizeWin), @@ -95,11 +60,9 @@ function getOptions(opts: WindowOptions): IWindowOptions { }; } -function setOptions(v: IWindowOptions & {[key: string]: boolean}, opts: WindowOptions): WindowOptions { +export function setWindowOptions(v: IWindowOptions & {[key: string]: boolean}, opts: WindowOptions): WindowOptions { for (const optionName in v) { const value = v[optionName]; - //const option = WindowOptions[optionName as any] as unknown as number; - //opts = value ? opts | option : opts & ~option; switch (optionName) { case 'restoreWin': opts = value ? opts | WindowOptions.restoreWin : opts & ~WindowOptions.restoreWin; break; case 'minimizeWin': opts = value ? opts | WindowOptions.minimizeWin : opts & ~WindowOptions.minimizeWin; break; @@ -128,10 +91,3 @@ function setOptions(v: IWindowOptions & {[key: string]: boolean}, opts: WindowOp } return opts; } - -declare const console: any; -let opts: WindowOptions = 0; - -opts = setOptions({refreshWin: true, pushTitle: true, popTitle: true}, opts); -console.log(opts); -console.log(getOptions(opts)); diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 3c3d4bea52..099c4501e2 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -7,6 +7,7 @@ import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'comm import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { clone } from 'common/Clone'; +import { setWindowOptions, getWindowOptions } from 'common/WindowOptions'; // Source: https://freesound.org/people/altemark/sounds/45759/ // This sound is released under the Creative Commons Attribution 3.0 Unported @@ -14,33 +15,6 @@ import { clone } from 'common/Clone'; // made, apart from the conversion to base64. export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; -// setting names controlled by allowedWindowOps -// not supported: GetChecksum, GetSelection, SetChecksum, SetSelection, SetXprop -export const WINDOW_OPTIONS: {[key: string]: number} = { - 'RestoreWin': 1, - 'MinimizeWin': 2, - 'SetWinPosition': 3, - 'SetWinSizePixels': 4, - 'RaiseWin': 5, - 'LowerWin': 6, - 'RefreshWin': 7, - 'SetWinSizeChars': 8, - 'MaximizeWin': 9, - 'FullscreenWin': 10, - 'GetWinState': 11, - 'GetWinPosition': 13, - 'GetWinSizePixels': 14, - 'GetScreenSizePixels': 15, // note: name not in xterm - 'GetCellSizePixels': 16, // note: name not in xterm - 'GetWinSizeChars': 18, - 'GetScreenSizeChars': 19, - 'GetIconTitle': 20, - 'GetWinTitle': 21, - 'PushTitle': 22, - 'PopTitle': 23, - 'SetWinLines': 24 // any param >= 24, also handles DECCOLM -}; - // TODO: Freeze? export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ @@ -79,7 +53,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ cancelEvents: false, useFlowControl: false, wordSeparator: ' ()[]{}\',:;"', - allowedWindowOps: [] + windowOptions: 0 }); /** @@ -157,20 +131,8 @@ export class OptionsService implements IOptionsService { throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`); } break; - case 'allowedWindowOps': - const values = (value as string).split(','); - const cleaned: number[] = []; - for (let i = 0; i < values.length; ++i) { - const option = parseInt(values[i].trim()) || WINDOW_OPTIONS[values[i].trim()]; - if (!option || option < 0 || option > 24) { - throw new Error(`unknown window option "${values[i]}"`); - } - if (!~cleaned.indexOf(option)) { - cleaned.push(option); - } - } - value = cleaned; - break; + case 'windowOptions': + value = setWindowOptions(value, this.options.windowOptions); } return value; } @@ -179,8 +141,8 @@ export class OptionsService implements IOptionsService { if (!(key in DEFAULT_OPTIONS)) { throw new Error(`No option with key "${key}"`); } - if (key === 'allowedWindowOps') { - return this.options[key].join(); + if (key === 'windowOptions') { + return getWindowOptions(this.options.windowOptions); } return this.options[key]; } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index f6444868ac..30650957de 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -7,6 +7,7 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; +import { WindowOptions } from 'common/WindowOptions'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { @@ -207,7 +208,7 @@ export interface IPartialTerminalOptions { theme?: ITheme; windowsMode?: boolean; wordSeparator?: string; - allowedWindowOps?: number[]; + windowOptions?: WindowOptions; } export interface ITerminalOptions { @@ -247,7 +248,7 @@ export interface ITerminalOptions { screenKeys: boolean; termName: string; useFlowControl: boolean; - allowedWindowOps: number[]; + windowOptions: WindowOptions; } export interface ITheme { @@ -273,3 +274,4 @@ export interface ITheme { brightCyan?: string; brightWhite?: string; } + diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3433bd1e61..0530bee718 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -440,7 +440,7 @@ declare module 'xterm' { /** * Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t". * Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t". - * Ps=14 has a default implementation. + * Ps=14 has a default implementation (also handles Ps=14 ; 2 if not overwritten by custom implementation). */ getWinSizePixels?: boolean; /** Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t". */ From 26052a29635302f6fbe31a913b5fff1184150568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 11:38:18 +0100 Subject: [PATCH 08/15] fix api test --- test/api/InputHandler.api.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index ef13c755ce..eb928b67fc 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -348,7 +348,6 @@ describe('InputHandler Integration Tests', function(): void { describe('Window Options - CSI Ps ; Ps ; Ps t', () => { it('should be disabled by default', async function() { - assert.equal(await page.evaluate(`(() => window.term.getOption('allowedWindowOps'))()`), ''); await page.evaluate(`(() => { window._stack = []; const _h = window.term.onData(data => window._stack.push(data)); @@ -359,13 +358,10 @@ describe('InputHandler Integration Tests', function(): void { window.term.write('\x1b[21t'); return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); })()`); - assert.deepEqual(await page.evaluate(`(() => _stack)()`), []); + await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), []); }); it('14 - GetWinSizePixels', async function() { - assert.equal(await page.evaluate(`(() => { - window.term.setOption('allowedWindowOps', 'GetWinSizePixels'); - return window.term.getOption('allowedWindowOps'); - })()`), '14'); + await page.evaluate(`window.term.setOption('windowOptions', {getWinSizePixels: true});`); await page.evaluate(`(() => { window._stack = []; const _h = window.term.onData(data => window._stack.push(data)); @@ -373,13 +369,10 @@ describe('InputHandler Integration Tests', function(): void { return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); })()`); const d = await getDimensions(); - assert.deepEqual(await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]); + await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]); }); it('16 - GetCellSizePixels', async function() { - assert.equal(await page.evaluate(`(() => { - window.term.setOption('allowedWindowOps', 'GetCellSizePixels'); - return window.term.getOption('allowedWindowOps'); - })()`), '16'); + await page.evaluate(`window.term.setOption('windowOptions', {getCellSizePixels: true});`); await page.evaluate(`(() => { window._stack = []; const _h = window.term.onData(data => window._stack.push(data)); @@ -387,7 +380,7 @@ describe('InputHandler Integration Tests', function(): void { return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); })); })()`); const d = await getDimensions(); - assert.deepEqual(await page.evaluate(`(() => _stack)()`), [`\x1b[6;${d.cellHeight};${d.cellWidth}t`]); + await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[6;${d.cellHeight};${d.cellWidth}t`]); }); }); }); From 45d1139ab0d65da874d9d99b37026e859270a5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 12:20:59 +0100 Subject: [PATCH 09/15] windowOptions on ctor options --- src/InputHandler.test.ts | 28 +++++++++------------------ src/InputHandler.ts | 8 ++++---- src/common/TestUtils.test.ts | 1 + src/common/Types.d.ts | 1 - src/common/services/OptionsService.ts | 12 ++++++++---- src/common/services/Services.ts | 7 ++++--- typings/xterm.d.ts | 6 ++++++ 7 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index afbef1682e..873998a6ad 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -17,7 +17,6 @@ import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsSer import { IBufferService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; -import { WindowOptions } from 'common/WindowOptions'; function getCursor(term: TestTerminal): number[] { return [ @@ -1270,7 +1269,6 @@ describe('InputHandler', () => { describe('windowOptions', () => { it('all should be disabled by default and not report', () => { const term = new TestTerminal({cols: 10, rows: 10}); - assert.deepEqual(term.options.windowOptions, 0); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[14t'); @@ -1281,8 +1279,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('14 - GetWinSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinSizePixels}); - assert.deepEqual(term.options.windowOptions, 1 << 14); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizePixels: true}}); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[14t'); @@ -1290,8 +1287,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('16 - GetCellSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getCellSizePixels}); - assert.deepEqual(term.options.windowOptions, 1 << 16); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getCellSizePixels: true}}); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[16t'); @@ -1299,8 +1295,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, []); }); it('18 - GetWinSizeChars', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinSizeChars}); - assert.deepEqual(term.options.windowOptions, 1 << 18); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizeChars: true}}); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b[18t'); @@ -1310,8 +1305,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); }); it('20 - GetIconTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getIconTitle}); - assert.deepEqual(term.options.windowOptions, 1 << 20); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getIconTitle: true}}); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b]1;hello world!\x07'); @@ -1322,8 +1316,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\', '\x1b]Lsome other\x1b\\']); }); it('21 - GetWinTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.getWinTitle}); - assert.deepEqual(term.options.windowOptions, 1 << 21); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinTitle: true}}); const stack: string[] = []; term.onData(data => stack.push(data)); term.writeSync('\x1b]2;hello world!\x07'); @@ -1334,8 +1327,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\', '\x1b]lsome other\x1b\\']); }); it('22/23 - PushTitle/PopTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); - assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1356,8 +1348,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); }); it('22/23 - PushTitle/PopTitle with ;1', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); - assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1378,8 +1369,7 @@ describe('InputHandler', () => { assert.deepEqual(stack, ['1', '2', '3']); }); it('22/23 - PushTitle/PopTitle with ;2', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.pushTitle | WindowOptions.popTitle}); - assert.deepEqual(term.options.windowOptions, (1 << 22) | (1 << 23)); + const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); const stack: string[] = []; term.onTitleChange(data => stack.push(data)); term.writeSync('\x1b]0;1\x07'); @@ -1407,7 +1397,7 @@ describe('InputHandler', () => { term.writeSync('\x1b[?3h'); assert.equal((term as any)._bufferService.cols, 10); // enabled - const term2 = new TestTerminal({cols: 10, rows: 10, windowOptions: WindowOptions.setWinLines}); + const term2 = new TestTerminal({cols: 10, rows: 10, windowOptions: {setWinLines: true}}); term2.writeSync('\x1b[?3l'); assert.equal((term2 as any)._bufferService.cols, 80); term2.writeSync('\x1b[?3h'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 21bab83b28..a2bb9be933 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -521,7 +521,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (id.final === 't' && !id.prefix && !id.intermediates) { // security: always check whether window option is allowed return this._parser.addCsiHandler(id, params => { - if (!hasWindowOption(params.params[0], this._optionsService.options.windowOptions)) { + if (!hasWindowOption(params.params[0], this._optionsService.windowOptions)) { return true; } return callback(params); @@ -1422,7 +1422,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This is only active if 'SetWinLines' (24) is listed * in `options.windowsOptions`. */ - if (this._optionsService.options.windowOptions & WindowOptions.setWinLines) { + if (this._optionsService.windowOptions & WindowOptions.setWinLines) { this._terminal.resize(132, this._bufferService.rows); this._terminal.reset(); } @@ -1609,7 +1609,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This is only active if 'SetWinLines' (24) is listed * in `options.windowsOptions`. */ - if (this._optionsService.options.windowOptions & WindowOptions.setWinLines) { + if (this._optionsService.windowOptions & WindowOptions.setWinLines) { this._terminal.resize(80, this._bufferService.rows); this._terminal.reset(); } @@ -2083,7 +2083,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps >= 24 not implemented */ public windowOptions(params: IParams): void { - if (!hasWindowOption(params.params[0], this._optionsService.options.windowOptions)) { + if (!hasWindowOption(params.params[0], this._optionsService.windowOptions)) { return; } const second = (params.length > 1) ? params.params[1] : 0; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 7d9c3aa600..42152fac53 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -75,6 +75,7 @@ export class MockLogService implements ILogService { export class MockOptionsService implements IOptionsService { serviceBrand: any; options: ITerminalOptions = clone(DEFAULT_OPTIONS); + windowOptions = 0; onOptionChange: IEvent = new EventEmitter().event; constructor(testOptions?: IPartialTerminalOptions) { if (testOptions) { diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 9b38aa3c94..2587820ae3 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -256,7 +256,6 @@ export type CoreMouseEncoding = (event: ICoreMouseEvent) => string; * WindowOption settings. */ export interface IWindowOptions { - [key: string]: boolean | undefined; restoreWin?: boolean; minimizeWin?: boolean; setWinPosition?: boolean; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 099c4501e2..9ded45e992 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -7,7 +7,7 @@ import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'comm import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { clone } from 'common/Clone'; -import { setWindowOptions, getWindowOptions } from 'common/WindowOptions'; +import { setWindowOptions, getWindowOptions, WindowOptions } from 'common/WindowOptions'; // Source: https://freesound.org/people/altemark/sounds/45759/ // This sound is released under the Creative Commons Attribution 3.0 Unported @@ -53,7 +53,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ cancelEvents: false, useFlowControl: false, wordSeparator: ' ()[]{}\',:;"', - windowOptions: 0 + windowOptions: {} }); /** @@ -65,6 +65,7 @@ export class OptionsService implements IOptionsService { serviceBrand: any; public options: ITerminalOptions; + public windowOptions: WindowOptions = 0; private _onOptionChange = new EventEmitter(); public get onOptionChange(): IEvent { return this._onOptionChange.event; } @@ -77,6 +78,9 @@ export class OptionsService implements IOptionsService { this.options[k] = newValue; } }); + if (options.windowOptions) { + this.windowOptions = setWindowOptions(options.windowOptions as any, 0); + } } public setOption(key: string, value: any): void { @@ -132,7 +136,7 @@ export class OptionsService implements IOptionsService { } break; case 'windowOptions': - value = setWindowOptions(value, this.options.windowOptions); + this.windowOptions = setWindowOptions(value, this.windowOptions); } return value; } @@ -142,7 +146,7 @@ export class OptionsService implements IOptionsService { throw new Error(`No option with key "${key}"`); } if (key === 'windowOptions') { - return getWindowOptions(this.options.windowOptions); + return getWindowOptions(this.windowOptions); } return this.options[key]; } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 30650957de..7a045fc7ea 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,7 +5,7 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, IWindowOptions } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { WindowOptions } from 'common/WindowOptions'; @@ -167,6 +167,7 @@ export interface IOptionsService { serviceBrand: any; readonly options: ITerminalOptions; + readonly windowOptions: WindowOptions; readonly onOptionChange: IEvent; @@ -208,7 +209,7 @@ export interface IPartialTerminalOptions { theme?: ITheme; windowsMode?: boolean; wordSeparator?: string; - windowOptions?: WindowOptions; + windowOptions?: IWindowOptions; } export interface ITerminalOptions { @@ -248,7 +249,7 @@ export interface ITerminalOptions { screenKeys: boolean; termName: string; useFlowControl: boolean; - windowOptions: WindowOptions; + windowOptions: IWindowOptions; } export interface ITheme { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0530bee718..e984bd44bb 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -215,6 +215,12 @@ declare module 'xterm' { * double click to select work logic. */ wordSeparator?: string; + + /** + * Enable various window manipulation and report features. + * All features are disabled by default for security reasons. + */ + windowOptions?: IWindowOptions; } /** From 8991ed82709acd4c19e762d14b49f08a83ddfe06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 12:59:48 +0100 Subject: [PATCH 10/15] simplify options --- src/InputHandler.ts | 44 +++++++++++-- src/common/WindowOptions.ts | 93 --------------------------- src/common/services/OptionsService.ts | 10 --- src/common/services/Services.ts | 2 - 4 files changed, 38 insertions(+), 111 deletions(-) delete mode 100644 src/common/WindowOptions.ts diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a2bb9be933..1cfa959060 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -18,11 +18,10 @@ import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionId import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IAttributeData, IDisposable } from 'common/Types'; +import { IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; -import { hasWindowOption, WindowOptions } from 'common/WindowOptions'; /** * Map collect to glevel. Used in `selectCharset`. @@ -39,6 +38,39 @@ const MAX_PARSEBUFFER_LENGTH = 131072; */ const STACK_LIMIT = 10; +// map params to window option +function paramToWindowOption(n: number, opts: IWindowOptions): boolean { + if (n > 24) { + return opts.setWinLines || false; + } + switch (n) { + case 1: return opts.restoreWin || false; + case 2: return opts.minimizeWin || false; + case 3: return opts.setWinPosition || false; + case 4: return opts.setWinSizePixels || false; + case 5: return opts.raiseWin || false; + case 6: return opts.lowerWin || false; + case 7: return opts.refreshWin || false; + case 8: return opts.setWinSizeChars || false; + case 9: return opts.maximizeWin || false; + case 10: return opts.fullscreenWin || false; + case 11: return opts.getWinState || false; + case 13: return opts.getWinPosition || false; + case 14: return opts.getWinSizePixels || false; + case 15: return opts.getScreenSizePixels || false; + case 16: return opts.getCellSizePixels || false; + case 18: return opts.getWinSizeChars || false; + case 19: return opts.getScreenSizeChars || false; + case 20: return opts.getIconTitle || false; + case 21: return opts.getWinTitle || false; + case 22: return opts.pushTitle || false; + case 23: return opts.popTitle || false; + case 24: return opts.setWinLines || false; + } + return false; +} + + /** * DCS subparser implementations @@ -521,7 +553,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (id.final === 't' && !id.prefix && !id.intermediates) { // security: always check whether window option is allowed return this._parser.addCsiHandler(id, params => { - if (!hasWindowOption(params.params[0], this._optionsService.windowOptions)) { + if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) { return true; } return callback(params); @@ -1422,7 +1454,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This is only active if 'SetWinLines' (24) is listed * in `options.windowsOptions`. */ - if (this._optionsService.windowOptions & WindowOptions.setWinLines) { + if (this._optionsService.options.windowOptions.setWinLines) { this._terminal.resize(132, this._bufferService.rows); this._terminal.reset(); } @@ -1609,7 +1641,7 @@ export class InputHandler extends Disposable implements IInputHandler { * This is only active if 'SetWinLines' (24) is listed * in `options.windowsOptions`. */ - if (this._optionsService.windowOptions & WindowOptions.setWinLines) { + if (this._optionsService.options.windowOptions.setWinLines) { this._terminal.resize(80, this._bufferService.rows); this._terminal.reset(); } @@ -2083,7 +2115,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps >= 24 not implemented */ public windowOptions(params: IParams): void { - if (!hasWindowOption(params.params[0], this._optionsService.windowOptions)) { + if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) { return; } const second = (params.length > 1) ? params.params[1] : 0; diff --git a/src/common/WindowOptions.ts b/src/common/WindowOptions.ts deleted file mode 100644 index 582a521520..0000000000 --- a/src/common/WindowOptions.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ -import { IWindowOptions } from 'common/Types'; - -export const enum WindowOptions { - restoreWin = 1 << 1, - minimizeWin = 1 << 2, - setWinPosition = 1 << 3, - setWinSizePixels = 1 << 4, - raiseWin = 1 << 5, - lowerWin = 1 << 6, - refreshWin = 1 << 7, - setWinSizeChars = 1 << 8, - maximizeWin = 1 << 9, - fullscreenWin = 1 << 10, - getWinState = 1 << 11, - getWinPosition = 1 << 13, - getWinSizePixels = 1 << 14, - getScreenSizePixels = 1 << 15, // note: name not in xterm - getCellSizePixels = 1 << 16, // note: name not in xterm - getWinSizeChars = 1 << 18, - getScreenSizeChars = 1 << 19, - getIconTitle = 1 << 20, - getWinTitle = 1 << 21, - pushTitle = 1 << 22, - popTitle = 1 << 23, - setWinLines = 1 << 24 // any param >= 24, also handles DECCOLM -} - -export function hasWindowOption(n: number, opts: WindowOptions): boolean { - return !!(opts & (1 << Math.min(n, 24))); -} - -export function getWindowOptions(opts: WindowOptions): IWindowOptions { - return { - restoreWin: !!(opts & WindowOptions.restoreWin), - minimizeWin: !!(opts & WindowOptions.minimizeWin), - setWinPosition: !!(opts & WindowOptions.setWinPosition), - setWinSizePixels: !!(opts & WindowOptions.setWinSizePixels), - raiseWin: !!(opts & WindowOptions.raiseWin), - lowerWin: !!(opts & WindowOptions.lowerWin), - refreshWin: !!(opts & WindowOptions.refreshWin), - setWinSizeChars: !!(opts & WindowOptions.setWinSizeChars), - maximizeWin: !!(opts & WindowOptions.maximizeWin), - fullscreenWin: !!(opts & WindowOptions.fullscreenWin), - getWinState: !!(opts & WindowOptions.getWinState), - getWinPosition: !!(opts & WindowOptions.getWinPosition), - getWinSizePixels: !!(opts & WindowOptions.getWinSizePixels), - getScreenSizePixels: !!(opts & WindowOptions.getScreenSizePixels), - getCellSizePixels: !!(opts & WindowOptions.getCellSizePixels), - getWinSizeChars: !!(opts & WindowOptions.getWinSizeChars), - getScreenSizeChars: !!(opts & WindowOptions.getScreenSizeChars), - getIconTitle: !!(opts & WindowOptions.getIconTitle), - getWinTitle: !!(opts & WindowOptions.getWinTitle), - pushTitle: !!(opts & WindowOptions.pushTitle), - popTitle: !!(opts & WindowOptions.popTitle), - setWinLines: !!(opts & WindowOptions.setWinLines) - }; -} - -export function setWindowOptions(v: IWindowOptions & {[key: string]: boolean}, opts: WindowOptions): WindowOptions { - for (const optionName in v) { - const value = v[optionName]; - switch (optionName) { - case 'restoreWin': opts = value ? opts | WindowOptions.restoreWin : opts & ~WindowOptions.restoreWin; break; - case 'minimizeWin': opts = value ? opts | WindowOptions.minimizeWin : opts & ~WindowOptions.minimizeWin; break; - case 'setWinPosition': opts = value ? opts | WindowOptions.setWinPosition : opts & ~WindowOptions.setWinPosition; break; - case 'setWinSizePixels': opts = value ? opts | WindowOptions.setWinSizePixels : opts & ~WindowOptions.setWinSizePixels; break; - case 'raiseWin': opts = value ? opts | WindowOptions.raiseWin : opts & ~WindowOptions.raiseWin; break; - case 'lowerWin': opts = value ? opts | WindowOptions.lowerWin : opts & ~WindowOptions.lowerWin; break; - case 'refreshWin': opts = value ? opts | WindowOptions.refreshWin : opts & ~WindowOptions.refreshWin; break; - case 'setWinSizeChars': opts = value ? opts | WindowOptions.setWinSizeChars : opts & ~WindowOptions.setWinSizeChars; break; - case 'maximizeWin': opts = value ? opts | WindowOptions.maximizeWin : opts & ~WindowOptions.maximizeWin; break; - case 'fullscreenWin': opts = value ? opts | WindowOptions.fullscreenWin : opts & ~WindowOptions.fullscreenWin; break; - case 'getWinState': opts = value ? opts | WindowOptions.getWinState : opts & ~WindowOptions.getWinState; break; - case 'getWinPosition': opts = value ? opts | WindowOptions.getWinPosition : opts & ~WindowOptions.getWinPosition; break; - case 'getWinSizePixels': opts = value ? opts | WindowOptions.getWinSizePixels : opts & ~WindowOptions.getWinSizePixels; break; - case 'getScreenSizePixels': opts = value ? opts | WindowOptions.getScreenSizePixels : opts & ~WindowOptions.getScreenSizePixels; break; - case 'getCellSizePixels': opts = value ? opts | WindowOptions.getCellSizePixels : opts & ~WindowOptions.getCellSizePixels; break; - case 'getWinSizeChars': opts = value ? opts | WindowOptions.getWinSizeChars : opts & ~WindowOptions.getWinSizeChars; break; - case 'getScreenSizeChars': opts = value ? opts | WindowOptions.getScreenSizeChars : opts & ~WindowOptions.getScreenSizeChars; break; - case 'getIconTitle': opts = value ? opts | WindowOptions.getIconTitle : opts & ~WindowOptions.getIconTitle; break; - case 'getWinTitle': opts = value ? opts | WindowOptions.getWinTitle : opts & ~WindowOptions.getWinTitle; break; - case 'pushTitle': opts = value ? opts | WindowOptions.pushTitle : opts & ~WindowOptions.pushTitle; break; - case 'popTitle': opts = value ? opts | WindowOptions.popTitle : opts & ~WindowOptions.popTitle; break; - case 'setWinLines': opts = value ? opts | WindowOptions.setWinLines : opts & ~WindowOptions.setWinLines; break; - default: throw new Error(`unknown WindowOption "${optionName}"`); - } - } - return opts; -} diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 9ded45e992..c05401d029 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -7,7 +7,6 @@ import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'comm import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { clone } from 'common/Clone'; -import { setWindowOptions, getWindowOptions, WindowOptions } from 'common/WindowOptions'; // Source: https://freesound.org/people/altemark/sounds/45759/ // This sound is released under the Creative Commons Attribution 3.0 Unported @@ -65,7 +64,6 @@ export class OptionsService implements IOptionsService { serviceBrand: any; public options: ITerminalOptions; - public windowOptions: WindowOptions = 0; private _onOptionChange = new EventEmitter(); public get onOptionChange(): IEvent { return this._onOptionChange.event; } @@ -78,9 +76,6 @@ export class OptionsService implements IOptionsService { this.options[k] = newValue; } }); - if (options.windowOptions) { - this.windowOptions = setWindowOptions(options.windowOptions as any, 0); - } } public setOption(key: string, value: any): void { @@ -135,8 +130,6 @@ export class OptionsService implements IOptionsService { throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`); } break; - case 'windowOptions': - this.windowOptions = setWindowOptions(value, this.windowOptions); } return value; } @@ -145,9 +138,6 @@ export class OptionsService implements IOptionsService { if (!(key in DEFAULT_OPTIONS)) { throw new Error(`No option with key "${key}"`); } - if (key === 'windowOptions') { - return getWindowOptions(this.windowOptions); - } return this.options[key]; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 7a045fc7ea..253e3110fb 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -7,7 +7,6 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, IWindowOptions } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; -import { WindowOptions } from 'common/WindowOptions'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { @@ -167,7 +166,6 @@ export interface IOptionsService { serviceBrand: any; readonly options: ITerminalOptions; - readonly windowOptions: WindowOptions; readonly onOptionChange: IEvent; From 7ad2a68052869bf3fd2d18e302a59bd4481e2791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 13:05:24 +0100 Subject: [PATCH 11/15] cleanup --- demo/client.ts | 2 +- src/common/TestUtils.test.ts | 1 - src/common/services/OptionsService.ts | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 42e4fe7d13..7118ec2ef3 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -288,7 +288,7 @@ function initOptions(term: TerminalType): void { }); html += '
'; Object.keys(stringOptions).forEach(o => { - if (stringOptions[o] && typeof stringOptions[o] !== 'string') { + if (stringOptions[o]) { html += `
`; } else { html += `
`; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 42152fac53..7d9c3aa600 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -75,7 +75,6 @@ export class MockLogService implements ILogService { export class MockOptionsService implements IOptionsService { serviceBrand: any; options: ITerminalOptions = clone(DEFAULT_OPTIONS); - windowOptions = 0; onOptionChange: IEvent = new EventEmitter().event; constructor(testOptions?: IPartialTerminalOptions) { if (testOptions) { diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index c05401d029..70a885fcd5 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -14,7 +14,6 @@ import { clone } from 'common/Clone'; // made, apart from the conversion to base64. export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; - // TODO: Freeze? export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ cols: 80, From b5fdd3b8d85c287624c7d569f9a11d2115ec3cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 7 Dec 2019 13:23:00 +0100 Subject: [PATCH 12/15] several fixes --- src/InputHandler.test.ts | 22 --------- src/InputHandler.ts | 14 ++---- src/Terminal.ts | 11 +---- src/common/Types.d.ts | 2 +- test/api/InputHandler.api.ts | 6 +-- typings/xterm.d.ts | 95 +++++++++++++++++++++++------------- 6 files changed, 69 insertions(+), 81 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 873998a6ad..f3238d0228 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1304,28 +1304,6 @@ describe('InputHandler', () => { term.writeSync('\x1b[18t'); assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); }); - it('20 - GetIconTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getIconTitle: true}}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b]1;hello world!\x07'); - term.writeSync('\x1b[20t'); - assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\']); - term.writeSync('\x1b]1;some other\x07'); - term.writeSync('\x1b[20t'); - assert.deepEqual(stack, ['\x1b]Lhello world!\x1b\\', '\x1b]Lsome other\x1b\\']); - }); - it('21 - GetWinTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinTitle: true}}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b]2;hello world!\x07'); - term.writeSync('\x1b[21t'); - assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\']); - term.writeSync('\x1b]2;some other\x07'); - term.writeSync('\x1b[21t'); - assert.deepEqual(stack, ['\x1b]lhello world!\x1b\\', '\x1b]lsome other\x1b\\']); - }); it('22/23 - PushTitle/PopTitle', () => { const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); const stack: string[] = []; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index be66f95d21..b98a62401e 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -2146,20 +2146,14 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.triggerDataEvent(`${C0.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`); } break; - case 20: // GetIconTitle, returns OSC L label ST - this._coreService.triggerDataEvent(`${C0.ESC}]L${this._iconName}${C0.ESC}\\`); - break; - case 21: // GetWinTitle, returns OSC l label ST - this._coreService.triggerDataEvent(`${C0.ESC}]l${this._windowTitle}${C0.ESC}\\`); - break; case 22: // PushTitle - if (!second || second === 2) { + if (second === 0 || second === 2) { this._windowTitleStack.push(this._windowTitle); if (this._windowTitleStack.length > STACK_LIMIT) { this._windowTitleStack.shift(); } } - if (!second || second === 1) { + if (second === 0 || second === 1) { this._iconNameStack.push(this._iconName); if (this._iconNameStack.length > STACK_LIMIT) { this._iconNameStack.shift(); @@ -2167,12 +2161,12 @@ export class InputHandler extends Disposable implements IInputHandler { } break; case 23: // PopTitle - if (!second || second === 2) { + if (second === 0 || second === 2) { if (this._windowTitleStack.length) { this.setTitle(this._windowTitleStack.pop()); } } - if (!second || second === 1) { + if (second === 0 || second === 1) { if (this._iconNameStack.length) { this.setIconName(this._iconNameStack.pop()); } diff --git a/src/Terminal.ts b/src/Terminal.ts index d402eab63d..255a810141 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -127,13 +127,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE; public sendFocus: boolean; - // misc public curAttrData: IAttributeData; private _eraseAttrData: IAttributeData; - public params: (string | number)[]; - public currentParam: string | number; - // write buffer private _writeBuffer: WriteBuffer; @@ -268,9 +264,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.curAttrData = DEFAULT_ATTR_DATA.clone(); this._eraseAttrData = DEFAULT_ATTR_DATA.clone(); - this.params = []; - this.currentParam = 0; - this._userScrolling = false; // Register input handler and refire/handle events @@ -1430,9 +1423,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an // invalid location - if (this.viewport) { - this.viewport.syncScrollArea(true); - } + this.viewport?.syncScrollArea(true); this.refresh(0, this.rows - 1); this._onResize.fire({ cols: x, rows: y }); diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 851a8ce8fc..a06a33cf78 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -256,7 +256,7 @@ export interface ICoreMouseProtocol { export type CoreMouseEncoding = (event: ICoreMouseEvent) => string; /** - * WindowOption settings. + * windowOptions */ export interface IWindowOptions { restoreWin?: boolean; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index eb928b67fc..ad30a51c11 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -443,11 +443,7 @@ async function getCursor(): Promise<{col: number, row: number}> { } async function getDimensions(): Promise { - const dim = await page.evaluate(` - (function() { - return term._core._renderService.dimensions; - })(); - `); + const dim = await page.evaluate(`term._core._renderService.dimensions`); return { cellWidth: dim.actualCellWidth.toFixed(0), cellHeight: dim.actualCellHeight.toFixed(0), diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 05cff40cb3..21f4127619 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -383,45 +383,63 @@ declare module 'xterm' { /** * Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t). * - * Note that most settings have no default implementation, as they heavily - * rely on the embedding environment. + * Most settings have no default implementation, as they heavily rely on + * the embedding environment. * - * To implement a features, create a custom CSI hook like this: - * ```Typescript + * To implement a feature, create a custom CSI hook like this: + * ```ts * term.parser.addCsiHandler({final: 't'}, params => { * const ps = params[0]; * switch (ps) { * case XY: - * ... // your implementation + * ... // your implementation for option XY * return true; // signal Ps=XY was handled * } * return false; // any Ps that was not handled * }); * ``` * - * All features are disabled by default for security reasons. - * All custom implementations are guarded by the boolean values automatically. + * Note on security: + * Most features are meant to deal with some information of the host machine + * where the terminal runs on. This is seen as a security risk possibly leaking + * sensitive data of the host to the program in the terminal. Therefore all options + * (even those without a default implementation) are guarded by the boolean flag + * and disabled by default. */ export interface IWindowOptions { - /** Ps=1 De-iconify window. */ + /** + * Ps=1 De-iconify window. + * No default implementation. + */ restoreWin?: boolean; - /** Ps=2 Iconify window. */ + /** + * Ps=2 Iconify window. + * No default implementation. + */ minimizeWin?: boolean; /** * Ps=3 ; x ; y - * Move window to [x, y]. + * Move window to [x, y]. + * No default implementation. */ setWinPosition?: boolean; /** * Ps = 4 ; height ; width * Resize the window to given `height` and `width` in pixels. * Omitted parameters should reuse the current height or width. - * Zero parameters should use the display's height or width. + * Zero parameters should use the display's height or width. + * No default implementation. */ setWinSizePixels?: boolean; - /** Ps=5 Raise the window to the front of the stacking order. */ + /** + * Ps=5 Raise the window to the front of the stacking order. + * No default implementation. + */ raiseWin?: boolean; - /** Ps=6 Lower the xterm window to the bottom of the stacking order. */ + /** + * Ps=6 Lower the xterm window to the bottom of the stacking order. + * No default implementation. + */ lowerWin?: boolean; /** Ps=7 Refresh the window. */ refreshWin?: boolean; @@ -429,78 +447,89 @@ declare module 'xterm' { * Ps = 8 ; height ; width * Resize the text area to given height and width in characters. * Omitted parameters should reuse the current height or width. - * Zero parameters use the display's height or width. + * Zero parameters use the display's height or width. + * No default implementation. */ setWinSizeChars?: boolean; /** * Ps=9 ; 0 Restore maximized window. * Ps=9 ; 1 Maximize window (i.e., resize to screen size). * Ps=9 ; 2 Maximize window vertically. - * Ps=9 ; 3 Maximize window horizontally. + * Ps=9 ; 3 Maximize window horizontally. + * No default implementation. */ maximizeWin?: boolean; /** * Ps=10 ; 0 Undo full-screen mode. * Ps=10 ; 1 Change to full-screen. - * Ps=10 ; 2 Toggle full-screen. + * Ps=10 ; 2 Toggle full-screen. + * No default implementation. */ fullscreenWin?: boolean; /** Ps=11 Report xterm window state. * If the xterm window is non-iconified, it returns "CSI 1 t". - * If the xterm window is iconified, it returns "CSI 2 t". + * If the xterm window is iconified, it returns "CSI 2 t". + * No default implementation. */ getWinState?: boolean; /** * Ps=13 Report xterm window position. Result is "CSI 3 ; x ; y t". - * Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t". + * Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t". + * No default implementation. */ getWinPosition?: boolean; /** * Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t". - * Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t". - * Ps=14 has a default implementation (also handles Ps=14 ; 2 if not overwritten by custom implementation). + * Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t". + * Has a default implementation. */ getWinSizePixels?: boolean; - /** Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t". */ + /** + * Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t". + * No default implementation. + */ getScreenSizePixels?: boolean; /** - * Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t". - * Implemented by default. + * Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t". + * Has a default implementation. */ getCellSizePixels?: boolean; /** - * Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t". - * Implemented by default. + * Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t". + * Has a default implementation. */ getWinSizeChars?: boolean; - /** Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t". */ + /** + * Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t". + * No default implementation. + */ getScreenSizeChars?: boolean; /** - * Ps=20 Report xterm window's icon label. Result is "OSC L label ST". - * Implemented by default. + * Ps=20 Report xterm window's icon label. Result is "OSC L label ST". + * No default implementation. */ getIconTitle?: boolean; /** - * Ps=21 Report xterm window's title. Result is "OSC l label ST". - * Implemented by default. + * Ps=21 Report xterm window's title. Result is "OSC l label ST". + * No default implementation. */ getWinTitle?: boolean; /** * Ps=22 ; 0 Save xterm icon and window title on stack. * Ps=22 ; 1 Save xterm icon title on stack. - * Ps=22 ; 2 Save xterm window title on stack. + * Ps=22 ; 2 Save xterm window title on stack. * All variants have a default implementation. */ pushTitle?: boolean; /** * Ps=23 ; 0 Restore xterm icon and window title from stack. * Ps=23 ; 1 Restore xterm icon title from stack. - * Ps=23 ; 2 Restore xterm window title from stack. + * Ps=23 ; 2 Restore xterm window title from stack. * All variants have a default implementation. */ popTitle?: boolean; /** - * Ps>=24 Resize to Ps lines (DECSLPP). + * Ps>=24 Resize to Ps lines (DECSLPP). * DECSLPP is not implemented. This settings is also used to * enable / disable DECCOLM (earlier variant of DECSLPP). */ From d7737f6c885d5ec5a2f1a8d836cbf0e6dd4a9ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 7 Dec 2019 13:39:06 +0100 Subject: [PATCH 13/15] cleanup --- src/InputHandler.ts | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index b98a62401e..cfebd48595 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -44,28 +44,28 @@ function paramToWindowOption(n: number, opts: IWindowOptions): boolean { return opts.setWinLines || false; } switch (n) { - case 1: return opts.restoreWin || false; - case 2: return opts.minimizeWin || false; - case 3: return opts.setWinPosition || false; - case 4: return opts.setWinSizePixels || false; - case 5: return opts.raiseWin || false; - case 6: return opts.lowerWin || false; - case 7: return opts.refreshWin || false; - case 8: return opts.setWinSizeChars || false; - case 9: return opts.maximizeWin || false; - case 10: return opts.fullscreenWin || false; - case 11: return opts.getWinState || false; - case 13: return opts.getWinPosition || false; - case 14: return opts.getWinSizePixels || false; - case 15: return opts.getScreenSizePixels || false; - case 16: return opts.getCellSizePixels || false; - case 18: return opts.getWinSizeChars || false; - case 19: return opts.getScreenSizeChars || false; - case 20: return opts.getIconTitle || false; - case 21: return opts.getWinTitle || false; - case 22: return opts.pushTitle || false; - case 23: return opts.popTitle || false; - case 24: return opts.setWinLines || false; + case 1: return !!opts.restoreWin; + case 2: return !!opts.minimizeWin; + case 3: return !!opts.setWinPosition; + case 4: return !!opts.setWinSizePixels; + case 5: return !!opts.raiseWin; + case 6: return !!opts.lowerWin; + case 7: return !!opts.refreshWin; + case 8: return !!opts.setWinSizeChars; + case 9: return !!opts.maximizeWin; + case 10: return !!opts.fullscreenWin; + case 11: return !!opts.getWinState; + case 13: return !!opts.getWinPosition; + case 14: return !!opts.getWinSizePixels; + case 15: return !!opts.getScreenSizePixels; + case 16: return !!opts.getCellSizePixels; + case 18: return !!opts.getWinSizeChars; + case 19: return !!opts.getScreenSizeChars; + case 20: return !!opts.getIconTitle; + case 21: return !!opts.getWinTitle; + case 22: return !!opts.pushTitle; + case 23: return !!opts.popTitle; + case 24: return !!opts.setWinLines; } return false; } From 489d8110c97a6315a22e55d616369f56e7efce57 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 4 Feb 2020 06:53:17 -0800 Subject: [PATCH 14/15] Move windowOptions into public API part --- src/common/services/OptionsService.ts | 6 +++--- src/common/services/Services.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 94462ec9ef..44a55d33da 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -45,13 +45,13 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ theme: {}, rightClickSelectsWord: isMac, rendererType: 'canvas', + windowOptions: {}, windowsMode: false, + wordSeparator: ' ()[]{}\',"`', convertEol: false, termName: 'xterm', - cancelEvents: false, - windowOptions: {}, - wordSeparator: ' ()[]{}\',"`' + cancelEvents: false }); /** diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 74d264d7fa..abc536a9c6 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -274,13 +274,13 @@ export interface ITerminalOptions { tabStopWidth: number; theme: ITheme; windowsMode: boolean; + windowOptions: IWindowOptions; wordSeparator: string; [key: string]: any; cancelEvents: boolean; convertEol: boolean; termName: string; - windowOptions: IWindowOptions; } export interface ITheme { From 3d2d35f5b66723a968b8eb76631b8a2e1f322ed0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 4 Feb 2020 07:01:25 -0800 Subject: [PATCH 15/15] Pull render service off instantiation service safely --- src/InputHandler.test.ts | 18 +++++++++--------- src/InputHandler.ts | 6 ++++-- src/Terminal.ts | 2 +- src/common/services/InstantiationService.ts | 6 +++++- src/common/services/Services.ts | 1 + 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 7bfbaf3b1d..de43022b15 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -44,7 +44,7 @@ describe('InputHandler', () => { bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; - const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); inputHandler.curAttrData.fg = 3; // Save cursor position inputHandler.saveCursor(); @@ -64,7 +64,7 @@ describe('InputHandler', () => { describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { const optionsService = new MockOptionsService(); - const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService(), {} as any); inputHandler.setCursorStyle(Params.fromArray([0])); assert.equal(optionsService.options['cursorStyle'], 'block'); @@ -105,7 +105,7 @@ describe('InputHandler', () => { it('should toggle Terminal.bracketedPasteMode', () => { const terminal = new MockInputHandlingTerminal(); terminal.bracketedPasteMode = false; - const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(terminal.bracketedPasteMode, true); @@ -124,7 +124,7 @@ describe('InputHandler', () => { it('insertChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -162,7 +162,7 @@ describe('InputHandler', () => { it('deleteChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -203,7 +203,7 @@ describe('InputHandler', () => { it('eraseInLine', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // fill 6 lines to test 3 different states inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -232,7 +232,7 @@ describe('InputHandler', () => { it('eraseInDisplay', function(): void { const term = new Terminal({cols: 80, rows: 7}); const bufferService = new MockBufferService(80, 7); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // fill display with a's for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -367,7 +367,7 @@ describe('InputHandler', () => { describe('print', () => { it('should not cause an infinite loop (regression test)', () => { const term = new Terminal(); - const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); @@ -382,7 +382,7 @@ describe('InputHandler', () => { beforeEach(() => { term = new Terminal(); bufferService = new MockBufferService(80, 30); - handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index d0007f4959..fb37f4ab1f 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -18,9 +18,10 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; import { IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IInstantiationService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; +import { IRenderService } from 'browser/services/Services'; /** * Map collect to glevel. Used in `selectCharset`. @@ -246,6 +247,7 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _optionsService: IOptionsService, private readonly _coreMouseService: ICoreMouseService, private readonly _unicodeService: IUnicodeService, + private readonly _instantiationService: IInstantiationService, private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); @@ -2459,7 +2461,7 @@ export class InputHandler extends Disposable implements IInputHandler { return; } const second = (params.length > 1) ? params.params[1] : 0; - const rs = (this._terminal as any)._renderService; // FIXME: import renderService to get rid of any type here + const rs = this._instantiationService.getService(IRenderService); switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t if (rs && second !== 2) { diff --git a/src/Terminal.ts b/src/Terminal.ts index a532c6dab1..1bcac1f056 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -245,7 +245,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._inputHandler.reset(); } else { // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService); + this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService, this._instantiationService); this._inputHandler.onRequestBell(() => this.bell()); this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); this._inputHandler.onRequestReset(() => this.reset()); diff --git a/src/common/services/InstantiationService.ts b/src/common/services/InstantiationService.ts index abce8ac54e..ade3bbe3b4 100644 --- a/src/common/services/InstantiationService.ts +++ b/src/common/services/InstantiationService.ts @@ -36,7 +36,7 @@ export class ServiceCollection { return this._entries.has(id); } - get(id: IServiceIdentifier): T { + get(id: IServiceIdentifier): T | undefined { return this._entries.get(id); } } @@ -52,6 +52,10 @@ export class InstantiationService implements IInstantiationService { this._services.set(id, instance); } + public getService(id: IServiceIdentifier): T | undefined { + return this._services.get(id); + } + public createInstance(ctor: any, ...args: any[]): any { const serviceDependencies = getServiceDependencies(ctor).sort((a, b) => a.index - b.index); diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index abc536a9c6..23b74e1444 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -171,6 +171,7 @@ export interface IConstructorSignature8 { export const IInstantiationService = createDecorator('InstantiationService'); export interface IInstantiationService { setService(id: IServiceIdentifier, instance: T): void; + getService(id: IServiceIdentifier): T | undefined; createInstance(ctor: IConstructorSignature0): T; createInstance(ctor: IConstructorSignature1, first: A1): T;