diff --git a/src/Terminal.ts b/src/Terminal.ts index 88712677a..230e7a926 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -40,7 +40,7 @@ import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; -import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, ICoreMouseEvent, CoreMouseEventType } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -142,7 +142,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public charsets: ICharset[]; // mouse properties - public mouseEvents: CoreMouseEventType[] = []; + public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE; public sendFocus: boolean; // misc @@ -650,8 +650,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.linkifier.attachToDom(this.element, this._mouseZoneManager); // apply mouse event classes set by escape codes before terminal was attached - this.element.classList.toggle('enable-mouse-events', !!this.mouseEvents.length); - if (this.mouseEvents.length) { + this.element.classList.toggle('enable-mouse-events', !!this.mouseEvents); + if (this.mouseEvents) { this._selectionService.disable(); } else { this._selectionService.enable(); @@ -703,11 +703,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } } - + // TODO: Move mouse event code into its own file. public bindMouse(): void { const self = this; const el = this.element; + // send event to CoreMouseService function sendEvent(ev: MouseEvent | WheelEvent): boolean { let pos; @@ -717,38 +718,43 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return false; } - let but: ICoreMouseEvent['button']; - let action: ICoreMouseEvent['action']; + let but: CoreMouseButton; + let action: CoreMouseAction; switch ((ev).overrideType || ev.type) { case 'mousemove': - action = 'move'; - but = 'none'; - if (!ev.buttons) { - but = 'none'; + 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 === 0 ? 'left' : ev.button === 1 ? 'middle' : ev.button === 2 ? 'right' : 'none'; + but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; } } else { - but = ev.buttons & 1 ? 'left' : ev.buttons & 4 ? 'middle' : ev.buttons & 2 ? 'right' : 'none'; + but = ev.buttons & 1 ? CoreMouseButton.LEFT : + ev.buttons & 4 ? CoreMouseButton.MIDDLE : + ev.buttons & 2 ? CoreMouseButton.RIGHT : + CoreMouseButton.NONE; } break; case 'mouseup': - action = 'up'; - but = ev.button === 0 ? 'left' : ev.button === 1 ? 'middle' : ev.button === 2 ? 'right' : 'none'; + action = CoreMouseAction.UP; + but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; break; case 'mousedown': - action = 'down'; - but = ev.button === 0 ? 'left' : ev.button === 1 ? 'middle' : ev.button === 2 ? 'right' : 'none'; + action = CoreMouseAction.DOWN; + but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; break; case 'DOMMouseScroll': - action = ev.detail < 0 ? 'up' : 'down'; - but = 'wheel'; + action = ev.detail < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN; + but = CoreMouseButton.WHEEL; break; case 'wheel': - action = (ev).deltaY < 0 ? 'up' : 'down'; - but = 'wheel'; + action = (ev).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN; + but = CoreMouseButton.WHEEL; break; + default: + // dont handle other event types by accident + return false; } return self._coreMouseService.triggerMouseEvent({ col: pos.x - 33, // FIXME: why -33 here? @@ -762,11 +768,11 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } /** - * Event handler state handling. + * Event listener state handling. * We listen to the onProtocolChange event of CoreMouseService and put - * requested handlers in `requestedEvents`. With this the handlers have - * all bits to do the event handler juggling. - * Note: 'mousedown' currently is an "always on" handler and not managed + * 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]: any} = { @@ -775,7 +781,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp mousedrag: null, mousemove: null }; - const eventHandlers: {[key: string]: any} = { + const eventListeners: {[key: string]: any} = { mouseup: (ev: MouseEvent) => { sendEvent(ev); if (!ev.buttons) { @@ -808,7 +814,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._coreMouseService.onProtocolChange(events => { // apply global changes on events this.mouseEvents = events; - if (events.length) { + if (events) { this._logService.info('Binding to mouse events:', events); this.element.classList.add('enable-mouse-events'); this._selectionService.disable(); @@ -820,49 +826,43 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // add/remove handlers from requestedEvents - if (events.indexOf('mousemove') === -1) { + if (!(events & CoreMouseEventType.MOVE)) { el.removeEventListener('mousemove', requestedEvents.mousemove); requestedEvents.mousemove = null; } else if (!requestedEvents.mousemove) { - el.addEventListener('mousemove', eventHandlers.mousemove); - requestedEvents.mousemove = eventHandlers.mousemove; + el.addEventListener('mousemove', eventListeners.mousemove); + requestedEvents.mousemove = eventListeners.mousemove; } - if (events.indexOf('wheel') === -1) { + if (!(events & CoreMouseEventType.WHEEL)) { el.removeEventListener('wheel', requestedEvents.wheel); requestedEvents.wheel = null; } else if (!requestedEvents.wheel) { - el.addEventListener('wheel', eventHandlers.wheel); - requestedEvents.wheel = eventHandlers.wheel; + el.addEventListener('wheel', eventListeners.wheel); + requestedEvents.wheel = eventListeners.wheel; } - if (events.indexOf('mouseup') === -1) { + if (!(events & CoreMouseEventType.UP)) { // always remove possible leftover handler this._document.removeEventListener('mouseup', requestedEvents.mouseup); requestedEvents.mouseup = null; } else if (!requestedEvents.mouseup) { - requestedEvents.mouseup = eventHandlers.mouseup; + requestedEvents.mouseup = eventListeners.mouseup; } - if (events.indexOf('mousedrag') === -1) { + if (!(events & CoreMouseEventType.DRAG)) { // always remove possible leftover handler this._document.removeEventListener('mousemove', requestedEvents.mousedrag); requestedEvents.mousedrag = null; } else if (!requestedEvents.mousedrag) { - requestedEvents.mousedrag = eventHandlers.mousedrag; + requestedEvents.mousedrag = eventListeners.mousedrag; } }); // force initial onProtocolChange so we dont miss early mouse requests this._coreMouseService.activeProtocol = this._coreMouseService.activeProtocol; - // DEBUG: get rid of annoying popup during testing - this.register(addDisposableDomListener(el, 'contextmenu', (e: any) => { - e.preventDefault(); - return false; - })); - /** - * "Always on" event handlers. + * "Always on" event listeners. */ this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => { ev.preventDefault(); @@ -871,7 +871,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Don't send the mouse button to the pty if mouse events are disabled or // if the selection manager is having selection forced (ie. a modifier is // held). - if (!this.mouseEvents.length || this._selectionService.shouldForceSelection(ev)) { + if (!this.mouseEvents || this._selectionService.shouldForceSelection(ev)) { return; } @@ -924,13 +924,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp })); this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => { - if (this.mouseEvents.length) return; + if (this.mouseEvents) return; this.viewport.onTouchStart(ev); return this.cancel(ev); })); this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => { - if (this.mouseEvents.length) return; + if (this.mouseEvents) return; this.viewport.onTouchMove(ev); return this.cancel(ev); })); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 5805ba3d2..f719aa88d 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -210,7 +210,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { x10Mouse: boolean; vt200Mouse: boolean; normalMouse: boolean; - mouseEvents: CoreMouseEventType[]; + mouseEvents: CoreMouseEventType; sendFocus: boolean; utfMouse: boolean; sgrMouse: boolean; diff --git a/src/Types.d.ts b/src/Types.d.ts index 80e8848ca..d74d29b96 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -36,7 +36,7 @@ export interface IInputHandlingTerminal { bracketedPasteMode: boolean; curAttrData: IAttributeData; savedCols: number; - mouseEvents: CoreMouseEventType[]; + mouseEvents: CoreMouseEventType; sendFocus: boolean; cursorHidden: boolean; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index bbeb4de84..62b5402d3 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -36,7 +36,7 @@ export class MockCoreMouseService implements ICoreMouseService { addProtocol(name: string): void {} reset(): void {} triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; } - onProtocolChange: IEvent = new EventEmitter().event; + onProtocolChange: IEvent = new EventEmitter().event; } export class MockCoreService implements ICoreService { diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 598a6a9eb..7f86b7ec0 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -162,6 +162,20 @@ export interface IRowRange { /** * Interface for mouse events in the core. */ +export const enum CoreMouseButton { + LEFT = 0, + MIDDLE = 1, + RIGHT = 2, + NONE = 3, + WHEEL = 64 +} + +export const enum CoreMouseAction { + UP = 0, + DOWN = 1, + MOVE = 32 +} + export interface ICoreMouseEvent { /** column (zero based). */ col: number; @@ -174,8 +188,8 @@ export interface ICoreMouseEvent { * There are invalid combinations of buttons and actions possible * (like move + wheel), those are silently ignored by the CoreMouseService. */ - button: 'left' | 'middle' | 'right' | 'wheel' | 'none'; - action: 'up' | 'down' | 'move'; + button: CoreMouseButton; + action: CoreMouseAction; /** * Modifier states. * Protocols will add/ignore those based on specific restrictions. @@ -190,14 +204,20 @@ export interface ICoreMouseEvent { * To be reported to the browser component which events a mouse * protocol wants to be catched and forwarded as an ICoreMouseEvent * to CoreMouseService. - * Known types: - * - mousedown: any mousedown event - * - mouseup: any mouseup event - * - wheel: any wheel event - * - mousedrag: any mousemove event while a button is pressed - * - mousemove: any mousemove event */ -export type CoreMouseEventType = 'mousedown' | 'mouseup' | 'wheel' | 'mousemove' | 'mousedrag'; +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. @@ -208,7 +228,7 @@ export type CoreMouseEventType = 'mousedown' | 'mouseup' | 'wheel' | 'mousemove' * modifiers or filtering invalid event types. */ export interface ICoreMouseProtocol { - events: CoreMouseEventType[]; + events: CoreMouseEventType; restrict: (e: ICoreMouseEvent) => boolean; } diff --git a/src/common/services/CoreMouseService.test.ts b/src/common/services/CoreMouseService.test.ts index 4f4392f17..63f39f5dc 100644 --- a/src/common/services/CoreMouseService.test.ts +++ b/src/common/services/CoreMouseService.test.ts @@ -5,7 +5,7 @@ import { CoreMouseService } from 'common/services/CoreMouseService'; import { MockCoreService, MockBufferService } from 'common/TestUtils.test'; import { assert } from 'chai'; -import { ICoreMouseEvent, CoreMouseEventType } from 'common/Types'; +import { ICoreMouseEvent, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; // needed mock services const bufferService = new MockBufferService(300, 100); @@ -56,18 +56,21 @@ describe('CoreMouseService', () => { }); it('addProtocol', () => { const cms = new CoreMouseService(bufferService, coreService); - cms.addProtocol('XYZ', { events: [], restrict: (e: ICoreMouseEvent) => false }); + 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[][] = []; + const wantedEvents: CoreMouseEventType[] = []; cms.onProtocolChange(events => wantedEvents.push(events)); cms.activeProtocol = 'NONE'; - assert.deepEqual(wantedEvents, [[]]); + assert.deepEqual(wantedEvents, [CoreMouseEventType.NONE]); cms.activeProtocol = 'ANY'; - assert.deepEqual(wantedEvents, [[], ['mousedown', 'mouseup', 'wheel', 'mousedrag', 'mousemove']]); + assert.deepEqual(wantedEvents, [ + CoreMouseEventType.NONE, + CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG | CoreMouseEventType.MOVE + ]); }); describe('triggerMouseEvent', () => { let cms: CoreMouseService; @@ -78,68 +81,68 @@ describe('CoreMouseService', () => { coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data); }); it('NONE', () => { - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), false); + 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: 'left', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), false); + 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: 'left', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), false); + 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: 'left', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), true); + 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: 'left', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), true); + 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: 'wheel', action: 'move' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'up' }), false); + 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: 'left', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 500, row: 0, button: 'left', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: -1, button: 'left', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 500, button: 'left', action: 'down' }), false); + 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: 'left', action: 'down' }), true); + 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]); @@ -152,7 +155,7 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'ANY'; cms.activeEncoding = 'UTF8'; for (let i = 0; i < bufferService.cols; ++i) { - assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + 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]); } }); @@ -160,7 +163,7 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'ANY'; cms.activeEncoding = 'SGR'; for (let i = 0; i < bufferService.cols; ++i) { - assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`); } }); @@ -168,7 +171,7 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'ANY'; cms.activeEncoding = 'URXVT'; for (let i = 0; i < bufferService.cols; ++i) { - assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); assert.deepEqual(reports.pop(), `\x1b[32;${i + 1};1M`); } }); @@ -177,36 +180,36 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'ANY'; cms.activeEncoding = 'DEFAULT'; // all buttons + down + no modifer - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'down', ctrl: false, alt: false, shift: false }), true); + 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: 'left', action: 'up', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'up', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'up', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up', ctrl: false, alt: false, shift: false }), true); + 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: 'left', action: 'move', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'move', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'move', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: false, shift: false }), true); + 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: 'none', action: 'move', ctrl: true, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: true, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: false, shift: true }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: true, alt: true, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: true, shift: true }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: true, alt: true, shift: true }), true); + 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 index bafb9b12a..22a565531 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -4,19 +4,19 @@ */ import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType } from 'common/Types'; +import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; /** * Supported default protocols. */ -const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { +const DEFAULT_PROTOCOLS: {[key: string]: ICoreMouseProtocol} = { /** * NONE * Events: none * Modifiers: none */ NONE: { - events: [], + events: CoreMouseEventType.NONE, restrict: () => false }, /** @@ -25,10 +25,10 @@ const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { * Modifiers: none (TBD) */ X10: { - events: ['mousedown'], + events: CoreMouseEventType.DOWN, restrict: (e: ICoreMouseEvent) => { // no wheel (TBD), no move, no up - if (e.button === 'wheel' || e.action !== 'down') { + if (e.button === CoreMouseButton.WHEEL || e.action !== CoreMouseAction.DOWN) { return false; } // no modifiers (TDB) @@ -44,10 +44,10 @@ const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { * Modifiers: CTRL (TBD) */ VT200: { - events: ['mousedown', 'mouseup', 'wheel'], + events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL, restrict: (e: ICoreMouseEvent) => { // no move - if (e.action === 'move') { + if (e.action === CoreMouseAction.MOVE) { return false; } // modifiers - only ctrl? @@ -62,10 +62,10 @@ const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { * Modifiers: CTRL | ALT | SHIFT */ DRAG: { - events: ['mousedown', 'mouseup', 'wheel', 'mousedrag'], + events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG, restrict: (e: ICoreMouseEvent) => { // no move without button - if (e.action === 'move' && e.button === 'none') { + if (e.action === CoreMouseAction.MOVE && e.button === CoreMouseButton.NONE) { return false; } // modifiers unclear - let all pass for now @@ -78,47 +78,32 @@ const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { * Modifiers: CTRL | ALT | SHIFT */ ANY: { - events: ['mousedown', 'mouseup', 'wheel', 'mousedrag', 'mousemove'], + events: + CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL + | CoreMouseEventType.DRAG | CoreMouseEventType.MOVE, restrict: (e: ICoreMouseEvent) => true } }; -/** - * Mapping of buttons and actions to event codes. (taken from xterm spec) - * More than 3 buttons are not supported. - */ -enum CODEMAP { - // buttons - left = 0, - middle = 1, - right = 2, - none = 3, - wheel = 64, - // actions - up = 0, - down = 1, - move = 32, - // modifiers - shift = 4, - alt = 8, - ctrl = 16 +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 { - const button = CODEMAP[e.button]; - const action = CODEMAP[e.action]; - const modifier = (e.ctrl ? CODEMAP.ctrl : 0) | (e.shift ? CODEMAP.shift : 0) | (e.alt ? CODEMAP.alt : 0); - let code = button | modifier; - if (e.button === 'wheel') { - code |= action; + const modifier = (e.ctrl ? Modifiers.CTRL : 0) | (e.shift ? Modifiers.SHIFT : 0) | (e.alt ? Modifiers.ALT : 0); + let code = e.button | modifier; + if (e.button === CoreMouseButton.WHEEL) { + code |= e.action; } else { - if (e.action === 'move') { - code |= CODEMAP.move; - } else if (e.action === 'up' && !isSGR) { + 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 |= CODEMAP.none; + code |= CoreMouseButton.NONE; } } return code; @@ -161,7 +146,7 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = { * Can report button on release and works with a well formed sequence. */ SGR: (e: ICoreMouseEvent) => { - const final = (e.action === 'up' && e.button !== 'wheel') ? 'm' : 'M'; + const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M'; return `\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`; }, /** @@ -195,7 +180,7 @@ export class CoreMouseService implements ICoreMouseService { private _encodings: {[name: string]: CoreMouseEncoding} = {}; private _activeProtocol: string = ''; private _activeEncoding: string = ''; - private _onProtocolChange = new EventEmitter(); + private _onProtocolChange = new EventEmitter(); private _lastEvent: ICoreMouseEvent | null = null; constructor( @@ -203,7 +188,7 @@ export class CoreMouseService implements ICoreMouseService { @ICoreService private readonly _coreService: ICoreService ) { // register default protocols and encodings - Object.keys(DEFAULT_PROCOTOLS).forEach(name => this.addProtocol(name, DEFAULT_PROCOTOLS[name])); + 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(); @@ -249,7 +234,7 @@ export class CoreMouseService implements ICoreMouseService { /** * Event to announce changes in mouse tracking. */ - public get onProtocolChange(): IEvent { + public get onProtocolChange(): IEvent { return this._onProtocolChange.event; } @@ -271,10 +256,10 @@ export class CoreMouseService implements ICoreMouseService { } // filter nonsense combinations of button + action - if (event.button === 'wheel' && event.action === 'move') { + if (event.button === CoreMouseButton.WHEEL && event.action === CoreMouseAction.MOVE) { return false; } - if (event.button === 'none' && event.action !== 'move') { + if (event.button === CoreMouseButton.NONE && event.action !== CoreMouseAction.MOVE) { return false; } @@ -283,7 +268,7 @@ export class CoreMouseService implements ICoreMouseService { event.row++; // debounce move at grid level - if (event.action === 'move' && this._lastEvent && this._compareEvents(this._lastEvent, event)) { + if (event.action === CoreMouseAction.MOVE && this._lastEvent && this._compareEvents(this._lastEvent, event)) { return false; } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 8c3a7eb80..614146561 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -46,7 +46,7 @@ export interface ICoreMouseService { /** * Event to announce changes in mouse tracking. */ - onProtocolChange: IEvent; + onProtocolChange: IEvent; } export const ICoreService = createDecorator('CoreService');