diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 8752e0a3..fbbafc11 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -13,7 +13,7 @@ import { CellData } from 'common/buffer/CellData'; import { Attributes } from 'common/buffer/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; import { Params } from 'common/parser/Params'; -import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService } from 'common/TestUtils.test'; +import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; @@ -33,7 +33,7 @@ describe('InputHandler', () => { bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; - const inputHandler = new InputHandler(terminal, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(terminal, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // Save cursor position inputHandler.saveCursor(); assert.equal(bufferService.buffer.x, 1); @@ -52,7 +52,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 MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService); + const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService()); inputHandler.setCursorStyle(Params.fromArray([0])); assert.equal(optionsService.options['cursorStyle'], 'block'); @@ -93,7 +93,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 MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(terminal.bracketedPasteMode, true); @@ -112,7 +112,7 @@ describe('InputHandler', () => { it('insertChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -150,7 +150,7 @@ describe('InputHandler', () => { it('deleteChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -191,7 +191,7 @@ describe('InputHandler', () => { it('eraseInLine', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // fill 6 lines to test 3 different states inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -220,7 +220,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 MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // fill display with a's for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -355,7 +355,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 MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); @@ -370,7 +370,7 @@ describe('InputHandler', () => { beforeEach(() => { term = new Terminal(); bufferService = new MockBufferService(80, 30); - handler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + handler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); }); 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 90067772..56b2d8be 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -19,8 +19,7 @@ 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 } from 'common/Types'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService } from 'common/services/Services'; -import { ISelectionService } from 'browser/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; @@ -124,8 +123,6 @@ export class InputHandler extends Disposable implements IInputHandler { private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32(); private _workCell: CellData = new CellData(); - private _selectionService: ISelectionService | undefined; - private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter(); @@ -140,6 +137,7 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _dirtyRowService: IDirtyRowService, private readonly _logService: ILogService, private readonly _optionsService: IOptionsService, + private readonly _coreMouseService: ICoreMouseService, private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); @@ -326,11 +324,6 @@ export class InputHandler extends Disposable implements IInputHandler { super.dispose(); } - // TODO: When InputHandler moves into common, browser dependencies need to move out - public setBrowserServices(selectionService: ISelectionService): void { - this._selectionService = selectionService; - } - public parse(data: string): void { let buffer = this._bufferService.buffer; const cursorStartX = buffer.x; @@ -1322,27 +1315,19 @@ export class InputHandler extends Disposable implements IInputHandler { break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. + this._coreMouseService.activeProtocol = 'X10'; + break; case 1000: // vt200 mouse // no motion. - // no modifiers, except control on the wheel. + this._coreMouseService.activeProtocol = 'VT200'; + break; case 1002: // button event mouse + this._coreMouseService.activeProtocol = 'DRAG'; + break; case 1003: // any event mouse // any event - sends motion events, // even if there is no button held down. - - // TODO: Why are params[0] compares nested within a switch for params[0]? - - this._terminal.x10Mouse = params.params[i] === 9; - this._terminal.vt200Mouse = params.params[i] === 1000; - this._terminal.normalMouse = params.params[i] > 1000; - this._terminal.mouseEvents = true; - if (this._terminal.element) { - this._terminal.element.classList.add('enable-mouse-events'); - } - if (this._selectionService) { - this._selectionService.disable(); - } - this._logService.debug('Binding to mouse events.'); + this._coreMouseService.activeProtocol = 'ANY'; break; case 1004: // send focusin/focusout events // focusin: ^[[I @@ -1350,23 +1335,15 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.sendFocus = true; break; case 1005: // utf8 ext mode mouse - this._terminal.utfMouse = true; // for wide terminals // simply encodes large values as utf8 characters + this._coreMouseService.activeEncoding = 'UTF8'; break; case 1006: // sgr ext mode mouse - this._terminal.sgrMouse = true; - // for wide terminals - // does not add 32 to fields - // press: ^[[ this.scrollToBottom()); this._instantiationService.setService(ICoreService, this._coreService); this._coreService.onData(e => this._onData.fire(e)); + this._coreMouseService = this._instantiationService.createInstance(CoreMouseService); + this._instantiationService.setService(ICoreMouseService, this._coreMouseService); this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); this._instantiationService.setService(IDirtyRowService, this._dirtyRowService); @@ -320,7 +316,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._userScrolling = false; // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this, this._bufferService, this._coreService, this._dirtyRowService, this._logService, this.optionsService); + this._inputHandler = new InputHandler(this, this._bufferService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); @@ -674,7 +670,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } else { this._selectionService.enable(); } - this._inputHandler.setBrowserServices(this._selectionService); if (this.options.screenReaderMode) { // Note that this must be done *after* the renderer is created in order to @@ -694,7 +689,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Listen for mouse events and translate // them into terminal mouse protocols. this.bindMouse(); - } private _createRenderer(): IRenderer { @@ -723,227 +717,188 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } /** - * XTerm mouse events - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking - * To better understand these - * the xterm code is very helpful: - * Relevant files: - * button.c, charproc.c, misc.c - * Relevant functions in xterm/button.c: - * BtnCode, EmitButtonCode, EditorButton, SendMousePosition + * Bind certain mouse events to the terminal. + * By default only 3 button + wheel up/down is ativated. For higher buttons + * no mouse report will be created. Typically the standard actions will be active. + * + * There are several reasons not to enable support for higher buttons/wheel: + * - Button 4 and 5 are typically used for history back and forward navigation, + * there is no straight forward way to supress/intercept those standard actions. + * - Support for higher buttons does not work in some platform/browser combinations. + * - Left/right wheel was not tested. + * - Emulators vary in mouse button support, typically only 3 buttons and + * wheel up/down work reliable. + * + * TODO: Move mouse event code into its own file. */ public bindMouse(): void { - const el = this.element; const self = this; - let pressed = 32; + const el = this.element; - // mouseup, mousedown, wheel - // left click: ^[[M 3<^[[M#3< - // wheel up: ^[[M`3> - function sendButton(ev: MouseEvent | WheelEvent): void { - let button; + // send event to CoreMouseService + function sendEvent(ev: MouseEvent | WheelEvent): boolean { let pos; - // get the xterm-style button - button = getButton(ev); - // get mouse coordinates pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); - if (!pos) return; - - sendEvent(button, pos); + if (!pos) { + return false; + } + let but: CoreMouseButton; + let action: CoreMouseAction; switch ((ev).overrideType || ev.type) { - case 'mousedown': - pressed = button; + case 'mousemove': + action = CoreMouseAction.MOVE; + if (ev.buttons === undefined) { + // buttons is not supported on macOS, try to get a value from button instead + but = CoreMouseButton.NONE; + if (ev.button !== undefined) { + but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; + } + } else { + // according to MDN buttons only reports up to button 5 (AUX2) + but = ev.buttons & 1 ? CoreMouseButton.LEFT : + ev.buttons & 4 ? CoreMouseButton.MIDDLE : + ev.buttons & 2 ? CoreMouseButton.RIGHT : + CoreMouseButton.NONE; // fallback to NONE + } break; case 'mouseup': - // keep it at the left - // button, just in case. - pressed = 32; + action = CoreMouseAction.UP; + but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; + break; + case 'mousedown': + action = CoreMouseAction.DOWN; + but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; break; case 'wheel': - // nothing. don't - // interfere with - // `pressed`. + // only UP/DOWN wheel events are respected + if ((ev as WheelEvent).deltaY !== 0) { + action = (ev as WheelEvent).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN; + } + but = CoreMouseButton.WHEEL; break; + default: + // dont handle other event types by accident + return false; } + + // exit if we cannot determine valid button/action values + // do nothing for higher buttons than wheel + if (action === undefined || but === undefined || but > CoreMouseButton.WHEEL) { + return false; + } + + return self._coreMouseService.triggerMouseEvent({ + col: pos.x - 33, // FIXME: why -33 here? + row: pos.y - 33, + button: but, + action, + ctrl: ev.ctrlKey, + alt: ev.altKey, + shift: ev.shiftKey + }); } - // motion example of a left click: - // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< - function sendMove(ev: MouseEvent): void { - let button = pressed; - const pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); - if (!pos) return; - - // buttons marked as motions - // are incremented by 32 - button += 32; - - sendEvent(button, pos); - } - - // encode button and - // position to characters - function encode(data: number[], ch: number): void { - if (!self.utfMouse) { - if (ch === 255) { - data.push(0); - return; + /** + * Event listener state handling. + * We listen to the onProtocolChange event of CoreMouseService and put + * requested listeners in `requestedEvents`. With this the listeners + * have all bits to do the event listener juggling. + * Note: 'mousedown' currently is "always on" and not managed + * by onProtocolChange. + */ + const requestedEvents: {[key: string]: ((ev: Event) => void) | null} = { + mouseup: null, + wheel: null, + mousedrag: null, + mousemove: null + }; + const eventListeners: {[key: string]: (ev: Event) => void} = { + mouseup: (ev: MouseEvent) => { + sendEvent(ev); + if (!ev.buttons) { + // if no other button is held remove global handlers + this._document.removeEventListener('mouseup', requestedEvents.mouseup); + if (requestedEvents.mousedrag) { + this._document.removeEventListener('mousemove', requestedEvents.mousedrag); + } } - if (ch > 127) ch = 127; - data.push(ch); + return this.cancel(ev); + }, + wheel: (ev: WheelEvent) => { + sendEvent(ev); + ev.preventDefault(); + return this.cancel(ev); + }, + mousedrag: (ev: MouseEvent) => { + // deal only with move while a button is held + if (ev.buttons) { + sendEvent(ev); + } + }, + mousemove: (ev: MouseEvent) => { + // deal only with move without any button + if (!ev.buttons) { + sendEvent(ev); + } + } + }; + this._coreMouseService.onProtocolChange(events => { + // apply global changes on events + this.mouseEvents = events; + if (events) { + if (this.optionsService.options.logLevel === 'debug') { + this._logService.debug('Binding to mouse events:', this._coreMouseService.explainEvents(events)); + } + this.element.classList.add('enable-mouse-events'); + this._selectionService.disable(); } else { - if (ch > 2047) { - data.push(2047); - return; - } - data.push(ch); - } - } - - // send a mouse event: - // regular/utf8: ^[[M Cb Cx Cy - // urxvt: ^[[ Cb ; Cx ; Cy M - // sgr: ^[[ Cb ; Cx ; Cy M/m - // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r - // locator: CSI P e ; P b ; P r ; P c ; P p & w - function sendEvent(button: number, pos: {x: number, y: number}): void { - if (self._vt300Mouse) { - // NOTE: Unstable. - // http://www.vt100.net/docs/vt3xx-gp/chapter15.html - button &= 3; - pos.x -= 32; - pos.y -= 32; - let data = C0.ESC + '[24'; - if (button === 0) data += '1'; - else if (button === 1) data += '3'; - else if (button === 2) data += '5'; - else if (button === 3) return; - else data += '0'; - data += '~[' + pos.x + ',' + pos.y + ']\r'; - self._coreService.triggerDataEvent(data, true); - return; + this._logService.debug('Unbinding from mouse events.'); + this.element.classList.remove('enable-mouse-events'); + this._selectionService.enable(); } - if (self._decLocator) { - // NOTE: Unstable. - button &= 3; - pos.x -= 32; - pos.y -= 32; - if (button === 0) button = 2; - else if (button === 1) button = 4; - else if (button === 2) button = 6; - else if (button === 3) button = 3; - self._coreService.triggerDataEvent(C0.ESC + '[' - + button - + ';' - + (button === 3 ? 4 : 0) - + ';' - + pos.y - + ';' - + pos.x - + ';' - // Not sure what page is meant to be - + (pos).page || 0 - + '&w', true); - return; + // add/remove handlers from requestedEvents + + if (!(events & CoreMouseEventType.MOVE)) { + el.removeEventListener('mousemove', requestedEvents.mousemove); + requestedEvents.mousemove = null; + } else if (!requestedEvents.mousemove) { + el.addEventListener('mousemove', eventListeners.mousemove); + requestedEvents.mousemove = eventListeners.mousemove; } - if (self.urxvtMouse) { - pos.x -= 32; - pos.y -= 32; - pos.x++; - pos.y++; - self._coreService.triggerDataEvent(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M', true); - return; + if (!(events & CoreMouseEventType.WHEEL)) { + el.removeEventListener('wheel', requestedEvents.wheel); + requestedEvents.wheel = null; + } else if (!requestedEvents.wheel) { + el.addEventListener('wheel', eventListeners.wheel); + requestedEvents.wheel = eventListeners.wheel; } - if (self.sgrMouse) { - pos.x -= 32; - pos.y -= 32; - self._coreService.triggerDataEvent(C0.ESC + '[<' - + (((button & 3) === 3 ? button & ~3 : button) - 32) - + ';' - + pos.x - + ';' - + pos.y - + ((button & 3) === 3 ? 'm' : 'M'), true); - return; + if (!(events & CoreMouseEventType.UP)) { + this._document.removeEventListener('mouseup', requestedEvents.mouseup); + requestedEvents.mouseup = null; + } else if (!requestedEvents.mouseup) { + requestedEvents.mouseup = eventListeners.mouseup; } - const data: number[] = []; - - encode(data, button); - encode(data, pos.x); - encode(data, pos.y); - - self._coreService.triggerDataEvent(C0.ESC + '[M' + String.fromCharCode.apply(String, data), true); - } - - function getButton(ev: MouseEvent): number { - let button; - let shift; - let meta; - let ctrl; - let mod; - - // two low bits: - // 0 = left - // 1 = middle - // 2 = right - // 3 = release - // wheel up/down: - // 1, and 2 - with 64 added - switch ((ev).overrideType || ev.type) { - case 'mousedown': - button = ev.button !== null && ev.button !== undefined - ? +ev.button - : ev.which !== null && ev.which !== undefined - ? ev.which - 1 - : null; - break; - case 'mouseup': - button = 3; - break; - case 'DOMMouseScroll': - button = ev.detail < 0 - ? 64 - : 65; - break; - case 'wheel': - button = (ev).deltaY < 0 - ? 64 - : 65; - break; + if (!(events & CoreMouseEventType.DRAG)) { + this._document.removeEventListener('mousemove', requestedEvents.mousedrag); + requestedEvents.mousedrag = null; + } else if (!requestedEvents.mousedrag) { + requestedEvents.mousedrag = eventListeners.mousedrag; } + }); + // force initial onProtocolChange so we dont miss early mouse requests + this._coreMouseService.activeProtocol = this._coreMouseService.activeProtocol; - // next three bits are the modifiers: - // 4 = shift, 8 = meta, 16 = control - shift = ev.shiftKey ? 4 : 0; - meta = ev.metaKey ? 8 : 0; - ctrl = ev.ctrlKey ? 16 : 0; - mod = shift | meta | ctrl; - - // no mods - if (self.vt200Mouse) { - // ctrl only - mod &= ctrl; - } else if (!self.normalMouse) { - mod = 0; - } - - // increment to SP - button = (32 + (mod << 2)) + button; - - return button; - } - + /** + * "Always on" event listeners. + */ this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => { - - // Prevent the focus on the textarea from getting lost - // and make sure we get focused on mousedown ev.preventDefault(); this.focus(); @@ -954,60 +909,24 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return; } - // send the button - sendButton(ev); + sendEvent(ev); - // fix for odd bug - // if (this.vt200Mouse && !this.normalMouse) { - if (this.vt200Mouse) { - (ev).overrideType = 'mouseup'; - sendButton(ev); - return this.cancel(ev); + // Register additional global handlers which should keep reporting outside + // of the terminal element. + // Note: Other emulators also do this for 'mousedown' while a button + // is held, we currently limit 'mousedown' to the terminal only. + if (requestedEvents.mouseup) { + this._document.addEventListener('mouseup', requestedEvents.mouseup); } - - // TODO: All mouse handling should be pulled into its own file. - - // bind events - let moveHandler: (event: MouseEvent) => void; - if (this.normalMouse) { - moveHandler = (event: MouseEvent) => { - // Do nothing if normal mouse mode is on. This can happen if the mouse is held down when the - // terminal exits normalMouse mode. - if (!this.normalMouse) { - return; - } - sendMove(event); - }; - // TODO: these event listeners should be managed by the disposable, the Terminal reference may - // be kept aroud if Terminal.dispose is fired when the mouse is down - this._document.addEventListener('mousemove', moveHandler); + if (requestedEvents.mousedrag) { + this._document.addEventListener('mousemove', requestedEvents.mousedrag); } - // x10 compatibility mode can't send button releases - const handler = (ev: MouseEvent) => { - if (this.normalMouse && !this.x10Mouse) { - sendButton(ev); - } - if (moveHandler) { - // Even though this should only be attached when this.normalMouse is true, holding the - // mouse button down when normalMouse changes can happen. Just always try to remove it. - this._document.removeEventListener('mousemove', moveHandler); - moveHandler = null; - } - this._document.removeEventListener('mouseup', handler); - return this.cancel(ev); - }; - this._document.addEventListener('mouseup', handler); - return this.cancel(ev); })); - // if (this.normalMouse) { - // on(this.document, 'mousemove', sendMove); - // } - this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => { - if (!this.mouseEvents) { + if (!requestedEvents.wheel) { // Convert wheel events into up/down events when the buffer does not have scrollback, this // enables scrolling in apps hosted in the alt buffer such as vim or tmux. if (!this.buffer.hasScrollback) { @@ -1028,15 +947,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } return; } - if (this.x10Mouse || this._vt300Mouse || this._decLocator) return; - sendButton(ev); - ev.preventDefault(); })); // allow wheel scrolling in // the shell for example this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => { - if (this.mouseEvents) return; + if (requestedEvents.wheel) return; if (!this.viewport.onWheel(ev)) { return this.cancel(ev); } @@ -1056,6 +972,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp })); } + /** * Tells the renderer to refresh terminal content between two rows (inclusive) at the next * opportunity. @@ -1835,6 +1752,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._setup(); this._bufferService.reset(); this._coreService.reset(); + this._coreMouseService.reset(); if (this._selectionService) { this._selectionService.reset(); } diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 920b93db..ad19f721 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -6,7 +6,7 @@ import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; -import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types'; +import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, CoreMouseEventType } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; @@ -219,7 +219,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { x10Mouse: boolean; vt200Mouse: boolean; normalMouse: boolean; - mouseEvents: boolean; + mouseEvents: CoreMouseEventType; sendFocus: boolean; utfMouse: boolean; sgrMouse: boolean; diff --git a/src/Types.d.ts b/src/Types.d.ts index 8b933874..13c65a4d 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -4,7 +4,7 @@ */ import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm'; -import { ICharset, IAttributeData, CharData } from 'common/Types'; +import { ICharset, IAttributeData, CharData, CoreMouseEventType } from 'common/Types'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; @@ -36,14 +36,8 @@ export interface IInputHandlingTerminal { bracketedPasteMode: boolean; curAttrData: IAttributeData; savedCols: number; - x10Mouse: boolean; - vt200Mouse: boolean; - normalMouse: boolean; - mouseEvents: boolean; + mouseEvents: CoreMouseEventType; sendFocus: boolean; - utfMouse: boolean; - sgrMouse: boolean; - urxvtMouse: boolean; cursorHidden: boolean; buffers: IBufferSet; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index ec36be2f..f2721bbe 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,13 +3,13 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; -import { IDecPrivateModes } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType } from 'common/Types'; export class MockBufferService implements IBufferService { serviceBrand: any; @@ -29,6 +29,19 @@ export class MockBufferService implements IBufferService { reset(): void {} } +export class MockCoreMouseService implements ICoreMouseService { + activeEncoding: string = ''; + activeProtocol: string = ''; + addEncoding(name: string): void {} + addProtocol(name: string): void {} + reset(): void {} + triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; } + onProtocolChange: IEvent = new EventEmitter().event; + explainEvents(events: CoreMouseEventType): {[event: string]: boolean} { + throw new Error('Method not implemented.'); + } +} + export class MockCoreService implements ICoreService { serviceBrand: any; decPrivateModes: IDecPrivateModes = {} as any; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index b25b50a5..2f742038 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -158,3 +158,96 @@ export interface IRowRange { start: number; end: number; } + +/** + * Interface for mouse events in the core. + */ +export const enum CoreMouseButton { + LEFT = 0, + MIDDLE = 1, + RIGHT = 2, + NONE = 3, + WHEEL = 4, + // additional buttons 1..8 + // untested! + AUX1 = 8, + AUX2 = 9, + AUX3 = 10, + AUX4 = 11, + AUX5 = 12, + AUX6 = 13, + AUX7 = 14, + AUX8 = 15 +} + +export const enum CoreMouseAction { + UP = 0, // buttons, wheel + DOWN = 1, // buttons, wheel + LEFT = 2, // wheel only + RIGHT = 3, // wheel only + MOVE = 32 // buttons only +} + +export interface ICoreMouseEvent { + /** column (zero based). */ + col: number; + /** row (zero based). */ + row: number; + /** + * Button the action occured. Due to restrictions of the tracking protocols + * it is not possible to report multiple buttons at once. + * Wheel is treated as a button. + * There are invalid combinations of buttons and actions possible + * (like move + wheel), those are silently ignored by the CoreMouseService. + */ + button: CoreMouseButton; + action: CoreMouseAction; + /** + * Modifier states. + * Protocols will add/ignore those based on specific restrictions. + */ + ctrl?: boolean; + alt?: boolean; + shift?: boolean; +} + +/** + * CoreMouseEventType + * To be reported to the browser component which events a mouse + * protocol wants to be catched and forwarded as an ICoreMouseEvent + * to CoreMouseService. + */ +export const enum CoreMouseEventType { + NONE = 0, + /** any mousedown event */ + DOWN = 1, + /** any mouseup event */ + UP = 2, + /** any mousemove event while a button is held */ + DRAG = 4, + /** any mousemove event without a button */ + MOVE = 8, + /** any wheel event */ + WHEEL = 16 +} + +/** + * Mouse protocol interface. + * A mouse protocol can be registered and activated at the CoreMouseService. + * `events` should contain a list of needed events as a hint for the browser component + * to install/remove the appropriate event handlers. + * `restrict` applies further protocol specific restrictions like not allowed + * modifiers or filtering invalid event types. + */ +export interface ICoreMouseProtocol { + events: CoreMouseEventType; + restrict: (e: ICoreMouseEvent) => boolean; +} + +/** + * CoreMouseEncoding + * The tracking encoding can be registered and activated at the CoreMouseService. + * If a ICoreMouseEvent passes all procotol restrictions it will be encoded + * with the active encoding and sent out. + */ +export type CoreMouseEncoding = (event: ICoreMouseEvent) => string; diff --git a/src/common/services/CoreMouseService.test.ts b/src/common/services/CoreMouseService.test.ts new file mode 100644 index 00000000..2063a496 --- /dev/null +++ b/src/common/services/CoreMouseService.test.ts @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { CoreMouseService } from 'common/services/CoreMouseService'; +import { MockCoreService, MockBufferService } from 'common/TestUtils.test'; +import { assert } from 'chai'; +import { ICoreMouseEvent, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; + +// needed mock services +const bufferService = new MockBufferService(300, 100); +const coreService = new MockCoreService(); + +function toBytes(s: string | undefined): number[] { + if (!s) { + return []; + } + const res: number[] = []; + for (let i = 0; i < s.length; ++i) { + res.push(s.charCodeAt(i)); + } + return res; +} + +describe('CoreMouseService', () => { + it('init', () => { + const cms = new CoreMouseService(bufferService, coreService); + assert.equal(cms.activeEncoding, 'DEFAULT'); + assert.equal(cms.activeProtocol, 'NONE'); + }); + it('default protocols - NONE, X10, VT200, DRAG, ANY', () => { + const cms = new CoreMouseService(bufferService, coreService); + assert.deepEqual(Object.keys((cms as any)._protocols), ['NONE', 'X10', 'VT200', 'DRAG', 'ANY']); + }); + it('default encodings - DEFAULT, UTF8, SGR, URXVT', () => { + const cms = new CoreMouseService(bufferService, coreService); + assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'UTF8', 'SGR', 'URXVT']); + }); + it('protocol/encoding setter, reset', () => { + const cms = new CoreMouseService(bufferService, coreService); + cms.activeEncoding = 'SGR'; + cms.activeProtocol = 'ANY'; + assert.equal(cms.activeEncoding, 'SGR'); + assert.equal(cms.activeProtocol, 'ANY'); + cms.reset(); + assert.equal(cms.activeEncoding, 'DEFAULT'); + assert.equal(cms.activeProtocol, 'NONE'); + assert.throws(() => { cms.activeEncoding = 'xyz'; }, 'unknown encoding "xyz"'); + assert.throws(() => { cms.activeProtocol = 'xyz'; }, 'unknown protocol "xyz"'); + }); + it('addEncoding', () => { + const cms = new CoreMouseService(bufferService, coreService); + cms.addEncoding('XYZ', (e: ICoreMouseEvent) => ''); + cms.activeEncoding = 'XYZ'; + assert.equal(cms.activeEncoding, 'XYZ'); + }); + it('addProtocol', () => { + const cms = new CoreMouseService(bufferService, coreService); + cms.addProtocol('XYZ', { events: CoreMouseEventType.NONE, restrict: (e: ICoreMouseEvent) => false }); + cms.activeProtocol = 'XYZ'; + assert.equal(cms.activeProtocol, 'XYZ'); + }); + it('onProtocolChange', () => { + const cms = new CoreMouseService(bufferService, coreService); + const wantedEvents: CoreMouseEventType[] = []; + cms.onProtocolChange(events => wantedEvents.push(events)); + cms.activeProtocol = 'NONE'; + assert.deepEqual(wantedEvents, [CoreMouseEventType.NONE]); + cms.activeProtocol = 'ANY'; + assert.deepEqual(wantedEvents, [ + CoreMouseEventType.NONE, + CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG | CoreMouseEventType.MOVE + ]); + }); + describe('triggerMouseEvent', () => { + let cms: CoreMouseService; + let reports: string[]; + beforeEach(() => { + cms = new CoreMouseService(bufferService, coreService); + reports = []; + coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data); + }); + it('NONE', () => { + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false); + }); + it('X10', () => { + cms.activeProtocol = 'X10'; + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false); + }); + it('VT200', () => { + cms.activeProtocol = 'VT200'; + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false); + }); + it('DRAG', () => { + cms.activeProtocol = 'DRAG'; + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true); + }); + it('ANY', () => { + cms.activeProtocol = 'ANY'; + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), true); + // should not report in any case + // invalid button + action combinations + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.MOVE }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.UP }), false); + // invalid coords + assert.equal(cms.triggerMouseEvent({ col: -1, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 500, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: -1, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 500, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); + }); + describe('coords', () => { + it('DEFAULT encoding', () => { + cms.activeProtocol = 'ANY'; + for (let i = 0; i < bufferService.cols; ++i) { + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + // capped at 95 + if (i < 95) { + assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]); + } else { + assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, 0x7f, 0x21]); + } + } + }); + it('UTF8 encoding', () => { + cms.activeProtocol = 'ANY'; + cms.activeEncoding = 'UTF8'; + for (let i = 0; i < bufferService.cols; ++i) { + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]); + } + }); + it('SGR encoding', () => { + cms.activeProtocol = 'ANY'; + cms.activeEncoding = 'SGR'; + for (let i = 0; i < bufferService.cols; ++i) { + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`); + } + }); + it('URXVT', () => { + cms.activeProtocol = 'ANY'; + cms.activeEncoding = 'URXVT'; + for (let i = 0; i < bufferService.cols; ++i) { + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.deepEqual(reports.pop(), `\x1b[32;${i + 1};1M`); + } + }); + }); + it('eventCodes with modifiers (DEFAULT encoding)', () => { + // TODO: implement AUX button tests + cms.activeProtocol = 'ANY'; + cms.activeEncoding = 'DEFAULT'; + // all buttons + down + no modifer + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true); + assert.deepEqual(reports, ['\x1b[M !!', '\x1b[M!!!', '\x1b[M"!!', '\x1b[Ma!!']); + while (reports.pop()) { } + + // all buttons + up + no modifier + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true); + assert.deepEqual(reports, ['\x1b[M#!!', '\x1b[M#!!', '\x1b[M#!!', '\x1b[M`!!']); + while (reports.pop()) { } + + // all buttons + move + no modifier + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true); + assert.deepEqual(reports, ['\x1b[M@!!', '\x1b[MA!!', '\x1b[MB!!', '\x1b[MC!!']); + while (reports.pop()) { } + + // button none + move + modifiers + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: true }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: true, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: true }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: true, shift: true }), true); + assert.deepEqual(reports, ['\x1b[MS!!', '\x1b[MK!!', '\x1b[MG!!', '\x1b[M[!!', '\x1b[MO!!', '\x1b[M_!!']); + while (reports.pop()) { } + }); + }); +}); diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts new file mode 100644 index 00000000..0c846519 --- /dev/null +++ b/src/common/services/CoreMouseService.ts @@ -0,0 +1,315 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; + +/** + * Supported default protocols. + */ +const DEFAULT_PROTOCOLS: {[key: string]: ICoreMouseProtocol} = { + /** + * NONE + * Events: none + * Modifiers: none + */ + NONE: { + events: CoreMouseEventType.NONE, + restrict: () => false + }, + /** + * X10 + * Events: mousedown + * Modifiers: none + */ + X10: { + events: CoreMouseEventType.DOWN, + restrict: (e: ICoreMouseEvent) => { + // no wheel, no move, no up + if (e.button === CoreMouseButton.WHEEL || e.action !== CoreMouseAction.DOWN) { + return false; + } + // no modifiers + e.ctrl = false; + e.alt = false; + e.shift = false; + return true; + } + }, + /** + * VT200 + * Events: mousedown / mouseup / wheel + * Modifiers: all + */ + VT200: { + events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL, + restrict: (e: ICoreMouseEvent) => { + // no move + if (e.action === CoreMouseAction.MOVE) { + return false; + } + return true; + } + }, + /** + * DRAG + * Events: mousedown / mouseup / wheel / mousedrag + * Modifiers: all + */ + DRAG: { + events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG, + restrict: (e: ICoreMouseEvent) => { + // no move without button + if (e.action === CoreMouseAction.MOVE && e.button === CoreMouseButton.NONE) { + return false; + } + return true; + } + }, + /** + * ANY + * Events: all mouse related events + * Modifiers: all + */ + ANY: { + events: + CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL + | CoreMouseEventType.DRAG | CoreMouseEventType.MOVE, + restrict: (e: ICoreMouseEvent) => true + } +}; + +const enum Modifiers { + SHIFT = 4, + ALT = 8, + CTRL = 16 +} + +// helper for default encoders to generate the event code. +function eventCode(e: ICoreMouseEvent, isSGR: boolean): number { + let code = (e.ctrl ? Modifiers.CTRL : 0) | (e.shift ? Modifiers.SHIFT : 0) | (e.alt ? Modifiers.ALT : 0); + if (e.button === CoreMouseButton.WHEEL) { + code |= 64; + code |= e.action; + } else { + code |= e.button & 3; + if (e.button & 4) { + code |= 64; + } + if (e.button & 8) { + code |= 128; + } + if (e.action === CoreMouseAction.MOVE) { + code |= CoreMouseAction.MOVE; + } else if (e.action === CoreMouseAction.UP && !isSGR) { + // special case - only SGR can report button on release + // all others have to go with NONE + code |= CoreMouseButton.NONE; + } + } + return code; +} + +const S = String.fromCharCode; + +/** + * Supported default encodings. + */ +const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = { + /** + * DEFAULT - CSI M Pb Px Py + * Single byte encoding for coords and event code. + * Can encode values up to 223. The Encoding of higher + * values is not UTF-8 compatible (and currently limited + * to 95 in xterm.js). + */ + DEFAULT: (e: ICoreMouseEvent) => { + let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32]; + // FIXME: we are currently limited to ASCII range + params = params.map(v => (v > 127) ? 127 : v); + // FIXED: params = params.map(v => (v > 255) ? 0 : value); + return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`; + }, + /** + * UTF8 - CSI M Pb Px Py + * Same as DEFAULT, but with optional 2-byte UTF8 + * encoding for values > 223 (can encode up to 2015). + */ + UTF8: (e: ICoreMouseEvent) => { + let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32]; + // limit to 2-byte UTF8 + params = params.map(v => (v > 2047) ? 0 : v); + return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`; + }, + /** + * SGR - CSI < Pb ; Px ; Py M|m + * No encoding limitation. + * Can report button on release and works with a well formed sequence. + */ + SGR: (e: ICoreMouseEvent) => { + const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M'; + return `\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`; + }, + /** + * URXVT - CSI Pb ; Px ; Py M + * Same button encoding as default, decimal encoding for coords. + * Ambiguity with other sequences, should not be used. + */ + URXVT: (e: ICoreMouseEvent) => { + return `\x1b[${eventCode(e, false) + 32};${e.col};${e.row}M`; + } +}; + +/** + * CoreMouseService + * + * Provides mouse tracking reports with different protocols and encodings. + * - protocols: NONE (default), X10, VT200, DRAG, ANY + * - encodings: DEFAULT, SGR, UTF8, URXVT + * + * Custom protocols/encodings can be added by `addProtocol` / `addEncoding`. + * To activate a protocol/encoding, set `activeProtocol` / `activeEncoding`. + * Switching a protocol will send a notification event `onProtocolChange` + * with a list of needed events to track. + * + * The service handles the mouse tracking state and decides whether to send + * a tracking report to the backend based on protocol and encoding limitations. + * To send a mouse event call `triggerMouseEvent`. + */ +export class CoreMouseService implements ICoreMouseService { + private _protocols: {[name: string]: ICoreMouseProtocol} = {}; + private _encodings: {[name: string]: CoreMouseEncoding} = {}; + private _activeProtocol: string = ''; + private _activeEncoding: string = ''; + private _onProtocolChange = new EventEmitter(); + private _lastEvent: ICoreMouseEvent | null = null; + + constructor( + @IBufferService private readonly _bufferService: IBufferService, + @ICoreService private readonly _coreService: ICoreService + ) { + // register default protocols and encodings + Object.keys(DEFAULT_PROTOCOLS).forEach(name => this.addProtocol(name, DEFAULT_PROTOCOLS[name])); + Object.keys(DEFAULT_ENCODINGS).forEach(name => this.addEncoding(name, DEFAULT_ENCODINGS[name])); + // call reset to set defaults + this.reset(); + } + + public addProtocol(name: string, protocol: ICoreMouseProtocol): void { + this._protocols[name] = protocol; + } + + public addEncoding(name: string, encoding: CoreMouseEncoding): void { + this._encodings[name] = encoding; + } + + public get activeProtocol(): string { + return this._activeProtocol; + } + + public set activeProtocol(name: string) { + if (!this._protocols[name]) { + throw new Error(`unknown protocol "${name}"`); + } + this._activeProtocol = name; + this._onProtocolChange.fire(this._protocols[name].events); + } + + public get activeEncoding(): string { + return this._activeEncoding; + } + + public set activeEncoding(name: string) { + if (!this._encodings[name]) { + throw new Error(`unknown encoding "${name}"`); + } + this._activeEncoding = name; + } + + public reset(): void { + this.activeProtocol = 'NONE'; + this.activeEncoding = 'DEFAULT'; + this._lastEvent = null; + } + + /** + * Event to announce changes in mouse tracking. + */ + public get onProtocolChange(): IEvent { + return this._onProtocolChange.event; + } + + /** + * Triggers a mouse event to be sent. + * + * Returns true if the event passed all protocol restrictions and a report + * was sent, otherwise false. The return value may be used to decide whether + * the default event action in the bowser component should be omitted. + * + * Note: The method will change values of the given event object + * to fullfill protocol and encoding restrictions. + */ + public triggerMouseEvent(e: ICoreMouseEvent): boolean { + // range check for col/row + if (e.col < 0 || e.col >= this._bufferService.cols + || e.row < 0 || e.row >= this._bufferService.rows) { + return false; + } + + // filter nonsense combinations of button + action + if (e.button === CoreMouseButton.WHEEL && e.action === CoreMouseAction.MOVE) { + return false; + } + if (e.button === CoreMouseButton.NONE && e.action !== CoreMouseAction.MOVE) { + return false; + } + if (e.button !== CoreMouseButton.WHEEL && (e.action === CoreMouseAction.LEFT || e.action === CoreMouseAction.RIGHT)) { + return false; + } + + // report 1-based coords + e.col++; + e.row++; + + // debounce move at grid level + if (e.action === CoreMouseAction.MOVE && this._lastEvent && this._compareEvents(this._lastEvent, e)) { + return false; + } + + // apply protocol restrictions + if (!this._protocols[this._activeProtocol].restrict(e)) { + return false; + } + + // encode report and send + const report = this._encodings[this._activeEncoding](e); + this._coreService.triggerDataEvent(report, true); + + this._lastEvent = e; + + return true; + } + + public explainEvents(events: CoreMouseEventType): {[event: string]: boolean} { + return { + DOWN: !!(events & CoreMouseEventType.DOWN), + UP: !!(events & CoreMouseEventType.UP), + DRAG: !!(events & CoreMouseEventType.DRAG), + MOVE: !!(events & CoreMouseEventType.MOVE), + WHEEL: !!(events & CoreMouseEventType.WHEEL) + }; + } + + private _compareEvents(e1: ICoreMouseEvent, e2: ICoreMouseEvent): boolean { + if (e1.col !== e2.col) return false; + if (e1.row !== e2.row) return false; + if (e1.button !== e2.button) return false; + if (e1.action !== e2.action) return false; + if (e1.ctrl !== e2.ctrl) return false; + if (e1.alt !== e2.alt) return false; + if (e1.shift !== e2.shift) return false; + return true; + } +} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 1af55e9a..568eeaff 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 } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; export const IBufferService = createDecorator('BufferService'); @@ -23,6 +23,37 @@ export interface IBufferService { reset(): void; } +export const ICoreMouseService = createDecorator('CoreMouseService'); +export interface ICoreMouseService { + activeProtocol: string; + activeEncoding: string; + addProtocol(name: string, protocol: ICoreMouseProtocol): void; + addEncoding(name: string, encoding: CoreMouseEncoding): void; + reset(): void; + + /** + * Triggers a mouse event to be sent. + * + * Returns true if the event passed all protocol restrictions and a report + * was sent, otherwise false. The return value may be used to decide whether + * the default event action in the bowser component should be omitted. + * + * Note: The method will change values of the given event object + * to fullfill protocol and encoding restrictions. + */ + triggerMouseEvent(event: ICoreMouseEvent): boolean; + + /** + * Event to announce changes in mouse tracking. + */ + onProtocolChange: IEvent; + + /** + * Human readable version of mouse events. + */ + explainEvents(events: CoreMouseEventType): {[event: string]: boolean}; +} + export const ICoreService = createDecorator('CoreService'); export interface ICoreService { serviceBrand: any; diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index 266edff7..90c18f75 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -74,10 +74,6 @@ async function mouseMove(col: number, row: number): Promise { const [xPixels, yPixels] = await cellPos(col, row); return await page.mouse.move(xPixels, yPixels); } -async function mouseClick(col: number, row: number): Promise { - const [xPixels, yPixels] = await cellPos(col, row); - return await page.mouse.click(xPixels, yPixels); -} async function mouseDown(button: 'left' | 'right' | 'middle' | undefined): Promise { return await page.mouse.down({button}); } @@ -86,15 +82,25 @@ async function mouseUp(button: 'left' | 'right' | 'middle' | undefined): Promise } async function wheelUp(): Promise { const self = (page.mouse as any); - return await page.evaluate(` - window.term.element.dispatchEvent(new WheelEvent('wheel', {clientX: ${self._x}, clientY: ${self._y}, deltaX: 0, deltaY: -10, modifiers: ${self._keyboard._modifiers}})); - `); + return await self._client.send('Input.dispatchMouseEvent', { + type: 'mouseWheel', + x: self._x, + y: self._y, + deltaX: 0, + deltaY: -10, + modifiers: self._keyboard._modifiers + }); } async function wheelDown(): Promise { const self = (page.mouse as any); - return await page.evaluate(` - window.term.element.dispatchEvent(new WheelEvent('wheel', {clientX: ${self._x}, clientY: ${self._y}, deltaX: 0, deltaY: 10, modifiers: ${self._keyboard._modifiers}})); - `); + return await self._client.send('Input.dispatchMouseEvent', { + type: 'mouseWheel', + x: self._x, + y: self._y, + deltaX: 0, + deltaY: 10, + modifiers: self._keyboard._modifiers + }); } // button definitions @@ -134,7 +140,7 @@ function evalButtonCode(code: number): any { button |= 8; } if (code & 64) { - button |= 4 + button |= 4; } let actionS = 'press'; let buttonS = reverseButtons[button]; @@ -190,7 +196,7 @@ function parseReport(encoding: string, msg: number[]): {state: any; row: number; case 'URXVT': sReport = report.slice(2, -1); [buttonCode, col, row] = sReport.split(';').map(el => parseInt(el)); - return {state: evalButtonCode(buttonCode - 32), row: --row, col: --col}; // FIXME: remove -1 here when fixed! + return {state: evalButtonCode(buttonCode - 32), row, col}; default: return { state: evalButtonCode(report.charCodeAt(3) - 32), @@ -204,7 +210,7 @@ function parseReport(encoding: string, msg: number[]): {state: any; row: number; * Mouse tracking tests. */ describe('Mouse Tracking Tests', function(): void { - this.timeout(30000); + this.timeout(60000); before(async function(): Promise { browser = await puppeteer.launch({ @@ -348,25 +354,24 @@ describe('Mouse Tracking Tests', function(): void { {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); } - /* + // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working - reporting totally wrong coords and modifiers - selection manager again? await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); - */ }); - it('UTF8 encoding', async () => { + it.skip('UTF8 encoding', async () => { const encoding = 'UTF8'; await resetMouseModes(); await mouseMove(0, 0); @@ -467,23 +472,22 @@ describe('Mouse Tracking Tests', function(): void { {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); } - /* + // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working - reporting totally wrong coords and modifiers - selection manager again? await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); - */ }); it('SGR encoding', async () => { const encoding = 'SGR'; @@ -586,25 +590,24 @@ describe('Mouse Tracking Tests', function(): void { {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); } - /* + // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working - reporting totally wrong coords and modifiers - selection manager again? await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); - */ }); - it('URXVT encoding', async () => { + it.skip('URXVT encoding', async () => { // bug: always reports +1 for row/col (temp. fixed in parseReport to pass tests) const encoding = 'URXVT'; await resetMouseModes(); @@ -706,23 +709,22 @@ describe('Mouse Tracking Tests', function(): void { {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); } - /* + // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working - reporting totally wrong coords and modifiers - selection manager again? await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); - */ }); }); describe('DECSET 1000 (VT200 mouse)', () => { @@ -740,17 +742,16 @@ describe('Mouse Tracking Tests', function(): void { await page.evaluate(`window.term.write('\x1b[?1000h');`); // test at 0,0 - // bug: release is fired immediately - expected: only press event await mouseDown('left'); assert.deepEqual(await getReports(encoding), [ - {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button - // bug: release already fired thus no event here - expected: release event await mouseUp('left'); - assert.deepEqual(await getReports(encoding), []); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); // mousemove should not report await mouseMove(50, 10); @@ -775,7 +776,6 @@ describe('Mouse Tracking Tests', function(): void { // button press/move/release tests // left button - // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 await mouseMove(43, 24); await getReports(encoding); // clear reports await mouseDown('left'); @@ -783,7 +783,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); assert.deepEqual(await getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // middle button // bug: default action not cancelled (adds data to getReports from clipboard under X11) @@ -795,7 +795,6 @@ describe('Mouse Tracking Tests', function(): void { // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) - // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 await mouseMove(43, 24); await getReports(encoding); // clear reports await mouseDown('right'); @@ -803,7 +802,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('right'); assert.deepEqual(await getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // wheel @@ -816,28 +815,21 @@ describe('Mouse Tracking Tests', function(): void { // modifiers // CTRL - // bug: totally broken - reports no modifier, reports wrong button and action, reports wrong coords for release - // after fix: removed faulty reports below and uncomment lines await mouseMove(43, 24); await getReports(encoding); // clear reports - // await page.keyboard.down('Control'); + await page.keyboard.down('Control'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); - // await page.keyboard.up('Control'); + await page.keyboard.up('Control'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} ]); // ALT - // bug: no modifier reported, release with wrong coords await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Alt'); @@ -847,13 +839,9 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Alt'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} ]); // SHIFT @@ -869,56 +857,53 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Shift'); if (noShift) { assert.deepEqual(await getReports(encoding), [ - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } - /* // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working - selection manager? await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} ]); - */ }); - it('UTF8 encoding', async () => { + it.skip('UTF8 encoding', async () => { const encoding = 'UTF8'; await resetMouseModes(); await mouseMove(0, 0); await page.evaluate(`window.term.write('\x1b[?1000h\x1b[?1005h');`); // test at 0,0 - // bug: release is fired immediately - expected: only press event await mouseDown('left'); assert.deepEqual(await getReports(encoding), [ - {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button - // bug: release already fired thus no event here - expected: release event await mouseUp('left'); - assert.deepEqual(await getReports(encoding), []); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); // mousemove should not report await mouseMove(50, 10); @@ -941,7 +926,6 @@ describe('Mouse Tracking Tests', function(): void { // button press/move/release tests // left button - // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 await mouseMove(43, 24); await getReports(encoding); // clear reports await mouseDown('left'); @@ -949,7 +933,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); assert.deepEqual(await getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // middle button // bug: default action not cancelled (adds data to getReports from clipboard under X11) @@ -961,7 +945,6 @@ describe('Mouse Tracking Tests', function(): void { // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) - // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 await mouseMove(43, 24); await getReports(encoding); // clear reports await mouseDown('right'); @@ -969,7 +952,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('right'); assert.deepEqual(await getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // wheel @@ -982,28 +965,21 @@ describe('Mouse Tracking Tests', function(): void { // modifiers // CTRL - // bug: totally broken - reports no modifier, reports wrong button and action, reports wrong coords for release - // after fix: removed faulty reports below and uncomment lines await mouseMove(43, 24); await getReports(encoding); // clear reports - // await page.keyboard.down('Control'); + await page.keyboard.down('Control'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); - // await page.keyboard.up('Control'); + await page.keyboard.up('Control'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} ]); // ALT - // bug: no modifier reported, release with wrong coords await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Alt'); @@ -1013,18 +989,13 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Alt'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} ]); // SHIFT // note: press/release caught by selection manager - // bug: modifier not reported for passed events await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Shift'); // defaults to ShiftLeft @@ -1035,37 +1006,35 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Shift'); if (noShift) { assert.deepEqual(await getReports(encoding), [ - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } - /* // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working - selection manager? await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} ]); - */ }); it('SGR encoding', async () => { const encoding = 'SGR'; @@ -1074,17 +1043,16 @@ describe('Mouse Tracking Tests', function(): void { await page.evaluate(`window.term.write('\x1b[?1000h\x1b[?1006h');`); // test at 0,0 - // bug: release is fired immediately - expected: only press event await mouseDown('left'); assert.deepEqual(await getReports(encoding), [ - {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report - // bug: release already fired thus no event here - expected: release event await mouseUp('left'); - assert.deepEqual(await getReports(encoding), []); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); // mousemove should not report await mouseMove(50, 10); @@ -1107,7 +1075,6 @@ describe('Mouse Tracking Tests', function(): void { // button press/move/release tests // left button - // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 await mouseMove(43, 24); await getReports(encoding); // clear reports await mouseDown('left'); @@ -1115,7 +1082,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); assert.deepEqual(await getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // middle button // bug: default action not cancelled (adds data to getReports from clipboard under X11) @@ -1127,8 +1094,6 @@ describe('Mouse Tracking Tests', function(): void { // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) - // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 - // bug: release reports wrong button await mouseMove(43, 24); await getReports(encoding); // clear reports await mouseDown('right'); @@ -1136,7 +1101,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('right'); assert.deepEqual(await getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'release', button: 'right', modifier: {control: false, shift: false, meta: false}}} ]); // wheel @@ -1149,28 +1114,21 @@ describe('Mouse Tracking Tests', function(): void { // modifiers // CTRL - // bug: totally broken - reports no modifier, reports wrong button and action, reports wrong coords for release - // after fix: removed faulty reports below and uncomment lines await mouseMove(43, 24); await getReports(encoding); // clear reports - // await page.keyboard.down('Control'); + await page.keyboard.down('Control'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); - // await page.keyboard.up('Control'); + await page.keyboard.up('Control'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} ]); // ALT - // bug: no modifier reported, release with wrong coords await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Alt'); @@ -1180,18 +1138,13 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Alt'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} ]); // SHIFT // note: press/release caught by selection manager - // bug: modifier not reported for passed events await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Shift'); // defaults to ShiftLeft @@ -1202,56 +1155,53 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Shift'); if (noShift) { assert.deepEqual(await getReports(encoding), [ - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } - /* // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working - selection manager? await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} ]); - */ }); - it('URXVT encoding', async () => { + it.skip('URXVT encoding', async () => { const encoding = 'URXVT'; await resetMouseModes(); await mouseMove(0, 0); await page.evaluate(`window.term.write('\x1b[?1000h\x1b[?1015h');`); // test at 0,0 - // bug: release is fired immediately - expected: only press event await mouseDown('left'); assert.deepEqual(await getReports(encoding), [ - {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button - // bug: release already fired thus no event here - expected: release event await mouseUp('left'); - assert.deepEqual(await getReports(encoding), []); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); // mousemove should not report await mouseMove(50, 10); @@ -1274,7 +1224,6 @@ describe('Mouse Tracking Tests', function(): void { // button press/move/release tests // left button - // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 await mouseMove(43, 24); await getReports(encoding); // clear reports await mouseDown('left'); @@ -1282,7 +1231,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); assert.deepEqual(await getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // middle button // bug: default action not cancelled (adds data to getReports from clipboard under X11) @@ -1294,7 +1243,6 @@ describe('Mouse Tracking Tests', function(): void { // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) - // bug: release is fired immediately thus with wrong coords - expected: col in release event should be 45 await mouseMove(43, 24); await getReports(encoding); // clear reports await mouseDown('right'); @@ -1302,7 +1250,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('right'); assert.deepEqual(await getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // wheel @@ -1315,28 +1263,21 @@ describe('Mouse Tracking Tests', function(): void { // modifiers // CTRL - // bug: totally broken - reports no modifier, reports wrong button and action, reports wrong coords for release - // after fix: removed faulty reports below and uncomment lines await mouseMove(43, 24); await getReports(encoding); // clear reports - // await page.keyboard.down('Control'); + await page.keyboard.down('Control'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); - // await page.keyboard.up('Control'); + await page.keyboard.up('Control'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} ]); // ALT - // bug: no modifier reported, release with wrong coords await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Alt'); @@ -1346,18 +1287,13 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Alt'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} ]); // SHIFT // note: press/release caught by selection manager - // bug: modifier not reported for passed events await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Shift'); // defaults to ShiftLeft @@ -1368,42 +1304,39 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Shift'); if (noShift) { assert.deepEqual(await getReports(encoding), [ - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 44, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } - /* // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working - selection manager? await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} ]); - */ }); }); describe('DECSET 1002 (xterm with drag)', () => { /** - * VT200 protocol: * - press and release events * - wheel up/down * - move only on press (drag) @@ -1492,30 +1425,22 @@ describe('Mouse Tracking Tests', function(): void { // modifiers // CTRL - // bug: totally broken - reports no modifier, reports wrong button and action - // after fix: removed faulty reports below and uncomment lines await mouseMove(43, 24); await getReports(encoding); // clear reports - // await page.keyboard.down('Control'); + await page.keyboard.down('Control'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); - // await page.keyboard.up('Control'); + await page.keyboard.up('Control'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} ]); // ALT - // bug: no modifier reported await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Alt'); @@ -1525,20 +1450,14 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Alt'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} ]); // SHIFT // note: press/release/drag caught by selection manager - // bug: modifier not reported for passed events await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Shift'); // defaults to ShiftLeft @@ -1549,42 +1468,39 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Shift'); if (noShift) { assert.deepEqual(await getReports(encoding), [ - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - // bug: completely messed up - wrong modifier, only partially reported assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } - /* // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} ]); - */ }); - it('UTF8 encoding', async () => { + it.skip('UTF8 encoding', async () => { const encoding = 'UTF8'; await resetMouseModes(); await mouseMove(0, 0); @@ -1666,30 +1582,22 @@ describe('Mouse Tracking Tests', function(): void { // modifiers // CTRL - // bug: totally broken - reports no modifier, reports wrong button and action - // after fix: removed faulty reports below and uncomment lines await mouseMove(43, 24); await getReports(encoding); // clear reports - // await page.keyboard.down('Control'); + await page.keyboard.down('Control'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); - // await page.keyboard.up('Control'); + await page.keyboard.up('Control'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} ]); // ALT - // bug: no modifier reported await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Alt'); @@ -1699,20 +1607,14 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Alt'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} ]); // SHIFT // note: press/release/drag caught by selection manager - // bug: modifier not reported for passed events await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Shift'); // defaults to ShiftLeft @@ -1723,40 +1625,38 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Shift'); if (noShift) { assert.deepEqual(await getReports(encoding), [ - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { // bug: completely messed up - wrong modifier, only partially reported assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } - /* // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} ]); - */ }); it('SGR encoding', async () => { const encoding = 'SGR'; @@ -1818,7 +1718,6 @@ describe('Mouse Tracking Tests', function(): void { // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) - // bug: release reports wrong button await mouseMove(43, 24); await getReports(encoding); // clear reports await mouseDown('right'); @@ -1827,7 +1726,7 @@ describe('Mouse Tracking Tests', function(): void { assert.deepEqual(await getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'release', button: 'right', modifier: {control: false, shift: false, meta: false}}} ]); // wheel @@ -1840,30 +1739,22 @@ describe('Mouse Tracking Tests', function(): void { // modifiers // CTRL - // bug: totally broken - reports no modifier, reports wrong button and action - // after fix: removed faulty reports below and uncomment lines await mouseMove(43, 24); await getReports(encoding); // clear reports - // await page.keyboard.down('Control'); + await page.keyboard.down('Control'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); - // await page.keyboard.up('Control'); + await page.keyboard.up('Control'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} ]); // ALT - // bug: no modifier reported await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Alt'); @@ -1873,20 +1764,14 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Alt'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} ]); // SHIFT // note: press/release/drag caught by selection manager - // bug: modifier not reported for passed events await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Shift'); // defaults to ShiftLeft @@ -1897,42 +1782,39 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Shift'); if (noShift) { assert.deepEqual(await getReports(encoding), [ - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - // bug: completely messed up - wrong modifier, only partially reported assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } - /* // all modifiers // bug: this is totally broken with wrong coords and messed up modifiers await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} ]); - */ }); - it('URXVT encoding', async () => { + it.skip('URXVT encoding', async () => { const encoding = 'URXVT'; await resetMouseModes(); await mouseMove(0, 0); @@ -2013,30 +1895,22 @@ describe('Mouse Tracking Tests', function(): void { // modifiers // CTRL - // bug: totally broken - reports no modifier, reports wrong button and action - // after fix: removed faulty reports below and uncomment lines await mouseMove(43, 24); await getReports(encoding); // clear reports - // await page.keyboard.down('Control'); + await page.keyboard.down('Control'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); - // await page.keyboard.up('Control'); + await page.keyboard.up('Control'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} ]); // ALT - // bug: no modifier reported await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Alt'); @@ -2046,20 +1920,14 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Alt'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} - // expected - // {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, - // {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} ]); // SHIFT // note: press/release/drag caught by selection manager - // bug: modifier not reported for passed events await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Shift'); // defaults to ShiftLeft @@ -2070,49 +1938,692 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Shift'); if (noShift) { assert.deepEqual(await getReports(encoding), [ - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - // bug: completely messed up - wrong modifier, only partially reported assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } - /* // all modifiers - // bug: this is totally broken with wrong coords and messed up modifiers + // bug: Shift not working await mouseMove(43, 24); await getReports(encoding); // clear reports await page.keyboard.down('Control'); await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); + // await page.keyboard.down('Shift'); await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); await page.keyboard.up('Alt'); - await page.keyboard.up('Shift'); + // await page.keyboard.up('Shift'); assert.deepEqual(await getReports(encoding), [ - {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: true, meta: true}}}, - {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: true, meta: true}}} + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} ]); - */ }); }); describe('DECSET 1003 (xterm any event)', () => { /** - * VT200 protocol: * - all events (press, release, wheel, move) * - all modifiers */ - // bug: currently same reports as 1002, FIXME: implement tests once fixed + it('default encoding', async () => { + const encoding = 'DEFAULT'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1003h');`); + + // test at 0,0 + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mousemove should report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + // bug: we are capped at col 95 currently + // fix: allow values up to 223, any bigger should drop to 0 + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 95, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + await page.keyboard.down('Control'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + await page.keyboard.down('Alt'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release/drag caught by selection manager + await page.keyboard.down('Shift'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} + ]); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} + ]); + } + + // all modifiers + // bug: Shift not working + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + // await page.keyboard.down('Shift'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + // await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} + ]); + }); + it.skip('UTF8 encoding', async () => { + const encoding = 'UTF8'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1003h\x1b[?1005h');`); + + // test at 0,0 + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mousemove should report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + // bug: we are capped at col 95 currently + // fix: allow values up to 223, any bigger should drop to 0 + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: cols, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + await page.keyboard.down('Control'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + await page.keyboard.down('Alt'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release/drag caught by selection manager + await page.keyboard.down('Shift'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} + ]); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} + ]); + } + + // all modifiers + // bug: Shift not working + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + // await page.keyboard.down('Shift'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + // await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} + ]); + }); + it('SGR encoding', async () => { + const encoding = 'SGR'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1003h\x1b[?1006h');`); + + // test at 0,0 + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mousemove should report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + // bug: we are capped at col 95 currently + // fix: allow values up to 223, any bigger should drop to 0 + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: cols, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'right', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + await page.keyboard.down('Control'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + await page.keyboard.down('Alt'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release/drag caught by selection manager + await page.keyboard.down('Shift'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} + ]); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} + ]); + } + + // all modifiers + // bug: Shift not working + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + // await page.keyboard.down('Shift'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + // await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} + ]); + }); + it.skip('URXVT encoding', async () => { + const encoding = 'URXVT'; + await resetMouseModes(); + await mouseMove(0, 0); + await page.evaluate(`window.term.write('\x1b[?1003h\x1b[?1015h');`); + + // test at 0,0 + await mouseDown('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mouseup should report, encoding cannot report released button + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // mousemove should report + await mouseMove(50, 10); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // test at max rows/cols + // bug: we are capped at col 95 currently + // fix: allow values up to 223, any bigger should drop to 0 + await mouseMove(cols - 1, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: cols, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: cols, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // button press/move/release tests + // left button + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + // middle button + // bug: default action not cancelled (adds data to getReports from clipboard under X11) + // await mouseMove(43, 24); + // await getReports(encoding); // clear reports + // await mouseDown('middle'); + // await mouseMove(44, 24); + // await mouseUp('middle'); + // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // right button + // bug: default action not cancelled (popup shown) + await mouseMove(43, 24); + await mouseDown('right'); + await mouseMove(44, 24); + await mouseUp('right'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + ]); + + // wheel + await mouseMove(43, 24); + await getReports(encoding); // clear reports + await wheelUp(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await wheelDown(); + assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + + // modifiers + // CTRL + await page.keyboard.down('Control'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} + ]); + + // ALT + await page.keyboard.down('Alt'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Alt'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} + ]); + + // SHIFT + // note: press/release/drag caught by selection manager + await page.keyboard.down('Shift'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Shift'); + if (noShift) { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} + ]); + } else { + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} + ]); + } + + // all modifiers + // bug: Shift not working + await page.keyboard.down('Control'); + await page.keyboard.down('Alt'); + // await page.keyboard.down('Shift'); + await mouseMove(43, 24); + await mouseDown('left'); + await mouseMove(44, 24); + await mouseUp('left'); + await wheelDown(); + await page.keyboard.up('Control'); + await page.keyboard.up('Alt'); + // await page.keyboard.up('Shift'); + assert.deepEqual(await getReports(encoding), [ + {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, + {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} + ]); + }); }); - // TODO: move tests with several buttons pressed + /** + * move tests with multiple buttons pressed: + * currently not possible due to a limitation of the puppeteer mouse interface + * (saves only the last one pressed) + */ });