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] 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 4eb0c2ac..42e4fe7d 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 21d6d742..afbef168 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 81413899..21bab83b 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 2f742038..9b38aa3c 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 a75faa63..582a5215 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 3c3d4bea..099c4501 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 f6444868..30650957 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 3433bd1e..0530bee7 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". */