+++ /dev/null
-/**
- * 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])}`;
- },
- /**
- * 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}`;
- }
-};
-
-/**
- * CoreMouseService
- *
- * Provides mouse tracking reports with different protocols and encodings.
- * - protocols: NONE (default), X10, VT200, DRAG, ANY
- * - encodings: DEFAULT, SGR (UTF8, URXVT removed in #2507)
- *
- * 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<CoreMouseEventType>();
- 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<CoreMouseEventType> {
- 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;
- }
-}