Merge pull request #5758 from Tyriar/5754__5756
CoreMouseService -> MouseStateService, make mouse services more focused
This commit is contained in:
@@ -78,7 +78,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
public browser: IBrowser = Browser as any;
|
||||
|
||||
private _customKeyEventHandler: CustomKeyEventHandler | undefined;
|
||||
private _customWheelEventHandler: CustomWheelEventHandler | undefined;
|
||||
|
||||
// Browser services
|
||||
private readonly _decorationService: DecorationService;
|
||||
@@ -582,7 +581,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
));
|
||||
this._instantiationService.setService(ISelectionService, this._selectionService);
|
||||
this._mouseService = this._instantiationService.createInstance(MouseService);
|
||||
this._mouseService.setCustomWheelEventHandler(this._customWheelEventHandler);
|
||||
this._instantiationService.setService(IMouseService, this._mouseService);
|
||||
this._register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
|
||||
this._register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
|
||||
@@ -607,7 +605,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this._register(addDisposableListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
|
||||
|
||||
// apply mouse event classes set by escape codes before terminal was attached
|
||||
if (this.coreMouseService.areMouseEventsActive) {
|
||||
if (this.mouseStateService.areMouseEventsActive) {
|
||||
this._selectionService.disable();
|
||||
this.element.classList.add('enable-mouse-events');
|
||||
} else {
|
||||
@@ -727,8 +725,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
|
||||
public attachCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler): void {
|
||||
this._customWheelEventHandler = customWheelEventHandler;
|
||||
this._mouseService?.setCustomWheelEventHandler(customWheelEventHandler);
|
||||
this.mouseStateService.setCustomWheelEventHandler(customWheelEventHandler);
|
||||
}
|
||||
|
||||
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
||||
@@ -1098,6 +1095,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
this._setup();
|
||||
super.reset();
|
||||
this._mouseService?.reset();
|
||||
this._selectionService?.reset();
|
||||
this._decorationService.reset();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, I
|
||||
import { Buffer } from 'common/buffer/Buffer';
|
||||
import * as Browser from 'common/Platform';
|
||||
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
|
||||
import { IUnicodeService, IOptionsService, ICoreService, ICoreMouseService } from 'common/services/Services';
|
||||
import { IUnicodeService, IOptionsService, ICoreService, IMouseStateService } from 'common/services/Services';
|
||||
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
|
||||
@@ -51,7 +51,7 @@ export class MockTerminal implements ITerminal {
|
||||
public dimensions: IRenderDimensionsApi | undefined;
|
||||
public markers!: IMarker[];
|
||||
public linkifier: ILinkifier2 | undefined;
|
||||
public coreMouseService!: ICoreMouseService;
|
||||
public mouseStateService!: IMouseStateService;
|
||||
public coreService!: ICoreService;
|
||||
public optionsService!: IOptionsService;
|
||||
public unicodeService!: IUnicodeService;
|
||||
@@ -393,13 +393,8 @@ export class MockMouseService implements IMouseService {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public setCustomWheelEventHandler(): void {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public bindMouse(): void {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
public bindMouse(): void { }
|
||||
public reset(): void { }
|
||||
}
|
||||
|
||||
export class MockRenderService implements IRenderService {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
||||
import { ViewportConstants } from 'browser/shared/Constants';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { IBufferService, ICoreMouseService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IMouseStateService, IOptionsService } from 'common/services/Services';
|
||||
import { CoreMouseEventType } from 'common/Types';
|
||||
import { addDisposableListener, scheduleAtNextAnimationFrame } from 'browser/Dom';
|
||||
import { SmoothScrollableElement } from 'browser/scrollable/scrollableElement';
|
||||
@@ -34,7 +34,7 @@ export class Viewport extends Disposable {
|
||||
screenElement: HTMLElement,
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@ICoreBrowserService coreBrowserService: ICoreBrowserService,
|
||||
@ICoreMouseService coreMouseService: ICoreMouseService,
|
||||
@IMouseStateService mouseStateService: IMouseStateService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@IRenderService private readonly _renderService: IRenderService
|
||||
@@ -65,7 +65,7 @@ export class Viewport extends Disposable {
|
||||
'scrollbar'
|
||||
], () => this._scrollableElement.updateOptions(this._getChangeOptions())));
|
||||
// Don't handle mouse wheel if wheel events are supported by the current mouse prototcol
|
||||
this._register(coreMouseService.onProtocolChange(type => {
|
||||
this._register(mouseStateService.onProtocolChange(type => {
|
||||
this._scrollableElement.updateOptions({
|
||||
handleMouseWheel: !(type & CoreMouseEventType.WHEEL)
|
||||
});
|
||||
|
||||
@@ -103,7 +103,7 @@ export class Terminal extends Disposable implements ITerminalApi {
|
||||
public get modes(): IModes {
|
||||
const m = this._core.coreService.decPrivateModes;
|
||||
let mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any' = 'none';
|
||||
switch (this._core.coreMouseService.activeProtocol) {
|
||||
switch (this._core.mouseStateService.activeProtocol) {
|
||||
case 'X10': mouseTrackingMode = 'x10'; break;
|
||||
case 'VT200': mouseTrackingMode = 'vt200'; break;
|
||||
case 'DRAG': mouseTrackingMode = 'drag'; break;
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { MouseService } from 'browser/services/MouseService';
|
||||
import { MouseStateService } from 'common/services/MouseStateService';
|
||||
import { CoreMouseAction, CoreMouseButton } from 'common/Types';
|
||||
import { IBufferService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
|
||||
import { MockCoreBrowserService, MockRenderService, MockSelectionService } from 'browser/TestUtils.test';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Minimal mocks for deps that MouseService touches in these tests
|
||||
const bufferService: IBufferService = {
|
||||
buffer: { hasScrollback: true } as any,
|
||||
cols: 500,
|
||||
rows: 500
|
||||
} as any;
|
||||
|
||||
const optionsService: IOptionsService = {
|
||||
rawOptions: {
|
||||
logLevel: 'info',
|
||||
fastScrollSensitivity: 1,
|
||||
scrollSensitivity: 1
|
||||
}
|
||||
} as any;
|
||||
|
||||
const logService: ILogService = {
|
||||
debug: () => {},
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
error: () => {}
|
||||
} as any;
|
||||
|
||||
describe('MouseService _triggerMouseEvent', () => {
|
||||
let mouseService: MouseService;
|
||||
let mouseStateService: MouseStateService;
|
||||
let coreService: ICoreService;
|
||||
let reports: string[];
|
||||
|
||||
beforeEach(() => {
|
||||
reports = [];
|
||||
mouseStateService = new MouseStateService();
|
||||
coreService = {
|
||||
triggerDataEvent: (data: string) => reports.push(data),
|
||||
triggerBinaryEvent: (data: string) => reports.push(data),
|
||||
decPrivateModes: { applicationCursorKeys: false }
|
||||
} as any;
|
||||
|
||||
mouseService = new MouseService(
|
||||
new MockRenderService(),
|
||||
{
|
||||
getMouseReportCoords: (_ev: MouseEvent, _el: HTMLElement) => ({ col: 0, row: 0, x: 0, y: 0 })
|
||||
} as any,
|
||||
mouseStateService,
|
||||
coreService,
|
||||
bufferService,
|
||||
optionsService,
|
||||
new MockSelectionService(),
|
||||
logService,
|
||||
new MockCoreBrowserService()
|
||||
);
|
||||
});
|
||||
|
||||
function trigger(e: Parameters<any>[0]): boolean {
|
||||
return (mouseService as any)._triggerMouseEvent(e);
|
||||
}
|
||||
|
||||
it('NONE', () => {
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);
|
||||
});
|
||||
|
||||
it('X10', () => {
|
||||
mouseStateService.activeProtocol = 'X10';
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);
|
||||
});
|
||||
|
||||
it('VT200', () => {
|
||||
mouseStateService.activeProtocol = 'VT200';
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);
|
||||
});
|
||||
|
||||
it('DRAG', () => {
|
||||
mouseStateService.activeProtocol = 'DRAG';
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);
|
||||
});
|
||||
|
||||
it('ANY', () => {
|
||||
mouseStateService.activeProtocol = 'ANY';
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), true);
|
||||
// should not report in any case
|
||||
// invalid button + action combinations
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.MOVE }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.UP }), false);
|
||||
// invalid coords
|
||||
assert.equal(trigger({ col: -1, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(trigger({ col: 500, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(trigger({ col: 0, row: -1, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(trigger({ col: 0, row: 500, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
});
|
||||
|
||||
describe('coords', () => {
|
||||
it('DEFAULT encoding', () => {
|
||||
mouseStateService.activeProtocol = 'ANY';
|
||||
for (let i = 0; i < bufferService.cols; ++i) {
|
||||
assert.equal(trigger({ col: i, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
if (i > 222) {
|
||||
// supress mouse reports if we are out of addressible range (max. 222)
|
||||
assert.deepEqual(toBytes(reports.pop()), []);
|
||||
} else {
|
||||
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('SGR encoding', () => {
|
||||
mouseStateService.activeProtocol = 'ANY';
|
||||
mouseStateService.activeEncoding = 'SGR';
|
||||
for (let i = 0; i < bufferService.cols; ++i) {
|
||||
assert.equal(trigger({ col: i, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`);
|
||||
}
|
||||
});
|
||||
|
||||
it('SGR_PIXELS encoding', () => {
|
||||
mouseStateService.activeProtocol = 'ANY';
|
||||
mouseStateService.activeEncoding = 'SGR_PIXELS';
|
||||
for (let i = 0; i < 500; ++i) {
|
||||
assert.equal(trigger({ col: 0, row: 0, x: i, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.deepEqual(reports.pop(), `\x1b[<0;${i};0M`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('eventCodes with modifiers (DEFAULT encoding)', () => {
|
||||
// TODO: implement AUX button tests
|
||||
mouseStateService.activeProtocol = 'ANY';
|
||||
mouseStateService.activeEncoding = 'DEFAULT';
|
||||
// all buttons + down + no modifer
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 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!!']);
|
||||
reports = [];
|
||||
|
||||
// all buttons + up + no modifier
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 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`!!']);
|
||||
reports = [];
|
||||
|
||||
// all buttons + move + no modifier
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 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!!']);
|
||||
reports = [];
|
||||
|
||||
// button none + move + modifiers
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: false, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: true }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: true, shift: false }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: true }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 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_!!']);
|
||||
reports = [];
|
||||
});
|
||||
});
|
||||
@@ -4,11 +4,10 @@
|
||||
*/
|
||||
|
||||
import { addDisposableListener } from 'browser/Dom';
|
||||
import { IBufferService, ICoreMouseService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
|
||||
import { CoreMouseAction, CoreMouseButton, CoreMouseEventType, IDisposable } from 'common/Types';
|
||||
import { IBufferService, IMouseStateService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
|
||||
import { CoreMouseAction, CoreMouseButton, CoreMouseEventType, ICoreMouseEvent, IDisposable } from 'common/Types';
|
||||
import { C0 } from 'common/data/EscapeSequences';
|
||||
import { toDisposable } from 'common/Lifecycle';
|
||||
import { CustomWheelEventHandler } from 'browser/Types';
|
||||
import { ICoreBrowserService, IMouseCoordsService, IMouseService, IMouseServiceTarget, IRenderService, ISelectionService } from './Services';
|
||||
|
||||
type RequestedMouseEvents = Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener | null>;
|
||||
@@ -22,12 +21,13 @@ interface IMouseBindContext {
|
||||
export class MouseService implements IMouseService {
|
||||
public serviceBrand: undefined;
|
||||
|
||||
private _customWheelEventHandler: CustomWheelEventHandler | undefined;
|
||||
private _lastEvent: ICoreMouseEvent | null = null;
|
||||
private _wheelPartialScroll: number = 0;
|
||||
|
||||
constructor(
|
||||
@IRenderService private readonly _renderService: IRenderService,
|
||||
@IMouseCoordsService private readonly _mouseCoordsService: IMouseCoordsService,
|
||||
@ICoreMouseService private readonly _coreMouseService: ICoreMouseService,
|
||||
@IMouseStateService private readonly _mouseStateService: IMouseStateService,
|
||||
@ICoreService private readonly _coreService: ICoreService,
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@@ -42,7 +42,7 @@ export class MouseService implements IMouseService {
|
||||
|
||||
/**
|
||||
* Event listener state handling.
|
||||
* We listen to the onProtocolChange event of CoreMouseService and put
|
||||
* We listen to the onProtocolChange event of MouseStateService 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
|
||||
@@ -61,11 +61,11 @@ export class MouseService implements IMouseService {
|
||||
mousedrag: (ev: Event) => this._handleMouseDrag(ctx, ev as MouseEvent),
|
||||
mousemove: (ev: Event) => this._handleMouseMove(ctx, ev as MouseEvent)
|
||||
};
|
||||
register(this._coreMouseService.onProtocolChange(events => {
|
||||
register(this._mouseStateService.onProtocolChange(events => {
|
||||
this._handleProtocolChange(ctx, eventListeners, events);
|
||||
}));
|
||||
// force initial onProtocolChange so we dont miss early mouse requests
|
||||
this._coreMouseService.activeProtocol = this._coreMouseService.activeProtocol;
|
||||
this._mouseStateService.activeProtocol = this._mouseStateService.activeProtocol;
|
||||
|
||||
// Ensure document-level listeners are removed on dispose
|
||||
register(toDisposable(() => {
|
||||
@@ -119,14 +119,14 @@ export class MouseService implements IMouseService {
|
||||
but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
|
||||
break;
|
||||
case 'wheel':
|
||||
if (this._customWheelEventHandler && this._customWheelEventHandler(ev as WheelEvent) === false) {
|
||||
if (!this._mouseStateService.allowCustomWheelEvent(ev as WheelEvent)) {
|
||||
return false;
|
||||
}
|
||||
const deltaY = (ev as WheelEvent).deltaY;
|
||||
if (deltaY === 0) {
|
||||
return false;
|
||||
}
|
||||
const lines = this._coreMouseService.consumeWheelEvent(
|
||||
const lines = this._consumeWheelEvent(
|
||||
ev as WheelEvent,
|
||||
this._renderService?.dimensions?.device?.cell?.height,
|
||||
this._coreBrowserService?.dpr
|
||||
@@ -148,7 +148,7 @@ export class MouseService implements IMouseService {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._coreMouseService.triggerMouseEvent({
|
||||
return this._triggerMouseEvent({
|
||||
col: pos.col,
|
||||
row: pos.row,
|
||||
x: pos.x,
|
||||
@@ -202,7 +202,7 @@ export class MouseService implements IMouseService {
|
||||
// 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._coreMouseService.areMouseEventsActive || this._selectionService.shouldForceSelection(ev)) {
|
||||
if (!this._mouseStateService.areMouseEventsActive || this._selectionService.shouldForceSelection(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ export class MouseService implements IMouseService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._customWheelEventHandler && this._customWheelEventHandler(ev) === false) {
|
||||
if (!this._mouseStateService.allowCustomWheelEvent(ev)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ export class MouseService implements IMouseService {
|
||||
return false;
|
||||
}
|
||||
|
||||
const lines = this._coreMouseService.consumeWheelEvent(
|
||||
const lines = this._consumeWheelEvent(
|
||||
ev,
|
||||
this._renderService?.dimensions?.device?.cell?.height,
|
||||
this._coreBrowserService?.dpr
|
||||
@@ -264,13 +264,18 @@ export class MouseService implements IMouseService {
|
||||
}
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._lastEvent = null;
|
||||
this._wheelPartialScroll = 0;
|
||||
}
|
||||
|
||||
private _handleProtocolChange(ctx: IMouseBindContext, eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener>, events: CoreMouseEventType): void {
|
||||
const { element, document } = ctx.target;
|
||||
const { requestedEvents } = ctx;
|
||||
// apply global changes on events
|
||||
if (events) {
|
||||
if (this._optionsService.rawOptions.logLevel === 'debug') {
|
||||
this._logService.debug('Binding to mouse events:', this._coreMouseService.explainEvents(events));
|
||||
this._logService.debug('Binding to mouse events:', this._explainEvents(events));
|
||||
}
|
||||
element.classList.add('enable-mouse-events');
|
||||
this._selectionService.disable();
|
||||
@@ -320,7 +325,131 @@ export class MouseService implements IMouseService {
|
||||
}
|
||||
}
|
||||
|
||||
public setCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler | undefined): void {
|
||||
this._customWheelEventHandler = customWheelEventHandler;
|
||||
private _applyScrollModifier(amount: number, ev: WheelEvent): number {
|
||||
// Multiply the scroll speed when the modifier key is pressed
|
||||
if (ev.altKey || ev.ctrlKey || ev.shiftKey) {
|
||||
return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity;
|
||||
}
|
||||
return amount * this._optionsService.rawOptions.scrollSensitivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a wheel event, accounting for partial scrolls for trackpad, mouse scrolls.
|
||||
* This prevents hyper-sensitive scrolling in alt buffer.
|
||||
*/
|
||||
private _consumeWheelEvent(ev: WheelEvent, cellHeight?: number, dpr?: number): number {
|
||||
// Do nothing if it's not a vertical scroll event
|
||||
if (ev.deltaY === 0 || ev.shiftKey) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cellHeight === undefined || dpr === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const targetWheelEventPixels = cellHeight / dpr;
|
||||
let amount = this._applyScrollModifier(ev.deltaY, ev);
|
||||
|
||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
|
||||
amount /= (targetWheelEventPixels + 0.0); // Prevent integer division
|
||||
|
||||
const isLikelyTrackpad = Math.abs(ev.deltaY) < 50;
|
||||
if (isLikelyTrackpad) {
|
||||
amount *= 0.3;
|
||||
}
|
||||
|
||||
this._wheelPartialScroll += amount;
|
||||
amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);
|
||||
this._wheelPartialScroll %= 1;
|
||||
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
||||
amount *= this._bufferService.rows;
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 browser component should be omitted.
|
||||
*
|
||||
* Note: The method will change values of the given event object
|
||||
* to fulfill protocol and encoding restrictions.
|
||||
*/
|
||||
private _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 events at grid or pixel level
|
||||
if (e.action === CoreMouseAction.MOVE
|
||||
&& this._lastEvent
|
||||
&& this._equalEvents(this._lastEvent, e, this._mouseStateService.isPixelEncoding)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// apply protocol restrictions
|
||||
if (!this._mouseStateService.restrictMouseEvent(e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// encode report and send
|
||||
const report = this._mouseStateService.encodeMouseEvent(e);
|
||||
if (report) {
|
||||
if (this._mouseStateService.isDefaultEncoding) {
|
||||
this._coreService.triggerBinaryEvent(report);
|
||||
} else {
|
||||
this._coreService.triggerDataEvent(report, true);
|
||||
}
|
||||
}
|
||||
|
||||
this._lastEvent = e;
|
||||
return true;
|
||||
}
|
||||
|
||||
private _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 _equalEvents(e1: ICoreMouseEvent, e2: ICoreMouseEvent, pixels: boolean): boolean {
|
||||
if (pixels) {
|
||||
if (e1.x !== e2.x) return false;
|
||||
if (e1.y !== e2.y) return false;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';
|
||||
import { CustomWheelEventHandler, IColorSet, ILink, ReadonlyColorSet } from 'browser/Types';
|
||||
import { IColorSet, ILink, ReadonlyColorSet } from 'browser/Types';
|
||||
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
|
||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||
import { AllColorIndex, IDisposable, IKeyboardResult } from 'common/Types';
|
||||
@@ -61,8 +61,8 @@ export const IMouseService = createDecorator<IMouseService>('MouseService');
|
||||
export interface IMouseService {
|
||||
serviceBrand: undefined;
|
||||
|
||||
setCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler | undefined): void;
|
||||
bindMouse(target: IMouseServiceTarget, register: (disposable: IDisposable) => void, focus: () => void): void;
|
||||
reset(): void;
|
||||
}
|
||||
export interface IMouseServiceTarget {
|
||||
element: HTMLElement;
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
* http://linux.die.net/man/7/urxvt
|
||||
*/
|
||||
|
||||
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services';
|
||||
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, IMouseStateService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services';
|
||||
import { InstantiationService } from 'common/services/InstantiationService';
|
||||
import { LogService } from 'common/services/LogService';
|
||||
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
|
||||
import { OptionsService } from 'common/services/OptionsService';
|
||||
import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent } from 'common/Types';
|
||||
import { CoreService } from 'common/services/CoreService';
|
||||
import { CoreMouseService } from 'common/services/CoreMouseService';
|
||||
import { MouseStateService } from 'common/services/MouseStateService';
|
||||
import { UnicodeService } from 'common/services/UnicodeService';
|
||||
import { CharsetService } from 'common/services/CharsetService';
|
||||
import { updateWindowsModeWrappedState } from 'common/WindowsMode';
|
||||
@@ -50,7 +50,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
protected readonly _charsetService: ICharsetService;
|
||||
protected readonly _oscLinkService: IOscLinkService;
|
||||
|
||||
public readonly coreMouseService: ICoreMouseService;
|
||||
public readonly mouseStateService: IMouseStateService;
|
||||
public readonly coreService: ICoreService;
|
||||
public readonly unicodeService: IUnicodeService;
|
||||
public readonly optionsService: IOptionsService;
|
||||
@@ -113,8 +113,8 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
this._instantiationService.setService(IBufferService, this._bufferService);
|
||||
this.coreService = this._register(this._instantiationService.createInstance(CoreService));
|
||||
this._instantiationService.setService(ICoreService, this.coreService);
|
||||
this.coreMouseService = this._register(this._instantiationService.createInstance(CoreMouseService));
|
||||
this._instantiationService.setService(ICoreMouseService, this.coreMouseService);
|
||||
this.mouseStateService = this._register(this._instantiationService.createInstance(MouseStateService));
|
||||
this._instantiationService.setService(IMouseStateService, this.mouseStateService);
|
||||
this.unicodeService = this._register(this._instantiationService.createInstance(UnicodeService));
|
||||
this._instantiationService.setService(IUnicodeService, this.unicodeService);
|
||||
this._charsetService = this._instantiationService.createInstance(CharsetService);
|
||||
@@ -124,7 +124,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
|
||||
|
||||
// Register input handler and handle/forward events
|
||||
this._inputHandler = this._register(new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService));
|
||||
this._inputHandler = this._register(new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.mouseStateService, this.unicodeService));
|
||||
this._register(EventUtils.forward(this._inputHandler.onLineFeed, this._onLineFeed));
|
||||
|
||||
// Setup listeners
|
||||
@@ -256,7 +256,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
this._bufferService.reset();
|
||||
this._charsetService.reset();
|
||||
this.coreService.reset();
|
||||
this.coreMouseService.reset();
|
||||
this.mouseStateService.reset();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { CellData } from 'common/buffer/CellData';
|
||||
import { Attributes, BgFlags, UnderlineStyle } from 'common/buffer/Constants';
|
||||
import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
|
||||
import { Params } from 'common/parser/Params';
|
||||
import { MockCoreService, MockBufferService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test';
|
||||
import { MockCoreService, MockBufferService, MockOptionsService, MockLogService, MockMouseStateService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test';
|
||||
import { IBufferService, ICoreService, type IOscLinkService } from 'common/services/Services';
|
||||
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
|
||||
import { clone } from 'common/Clone';
|
||||
@@ -70,7 +70,7 @@ describe('InputHandler', () => {
|
||||
coreService = new CoreService(bufferService, new MockLogService(), optionsService);
|
||||
oscLinkService = new OscLinkService(bufferService);
|
||||
|
||||
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, oscLinkService, new MockCoreMouseService(), new MockUnicodeService());
|
||||
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, oscLinkService, new MockMouseStateService(), new MockUnicodeService());
|
||||
});
|
||||
|
||||
describe('SL/SR/DECIC/DECDC', () => {
|
||||
@@ -261,7 +261,7 @@ describe('InputHandler', () => {
|
||||
describe('setMode', () => {
|
||||
it('should toggle bracketedPasteMode', () => {
|
||||
const coreService = new MockCoreService();
|
||||
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
|
||||
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());
|
||||
// Set bracketed paste mode
|
||||
inputHandler.setModePrivate(Params.fromArray([2004]));
|
||||
assert.equal(coreService.decPrivateModes.bracketedPasteMode, true);
|
||||
@@ -272,7 +272,7 @@ describe('InputHandler', () => {
|
||||
it('should toggle colorSchemeUpdates (DECSET 2031)', () => {
|
||||
const coreService = new MockCoreService();
|
||||
const optionsService = new MockOptionsService();
|
||||
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
|
||||
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());
|
||||
// Set color scheme updates mode (default colorSchemeQuery=true)
|
||||
inputHandler.setModePrivate(Params.fromArray([2031]));
|
||||
assert.equal(coreService.decPrivateModes.colorSchemeUpdates, true);
|
||||
@@ -284,7 +284,7 @@ describe('InputHandler', () => {
|
||||
const coreService = new MockCoreService();
|
||||
const optionsService = new MockOptionsService();
|
||||
optionsService.rawOptions.vtExtensions = { colorSchemeQuery: false };
|
||||
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
|
||||
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());
|
||||
// Attempt to set color scheme updates mode
|
||||
inputHandler.setModePrivate(Params.fromArray([2031]));
|
||||
assert.equal(coreService.decPrivateModes.colorSchemeUpdates, false);
|
||||
@@ -306,7 +306,7 @@ describe('InputHandler', () => {
|
||||
new MockLogService(),
|
||||
new MockOptionsService(),
|
||||
new MockOscLinkService(),
|
||||
new MockCoreMouseService(),
|
||||
new MockMouseStateService(),
|
||||
new MockUnicodeService()
|
||||
);
|
||||
|
||||
@@ -352,7 +352,7 @@ describe('InputHandler', () => {
|
||||
new MockLogService(),
|
||||
new MockOptionsService(),
|
||||
new MockOscLinkService(),
|
||||
new MockCoreMouseService(),
|
||||
new MockMouseStateService(),
|
||||
new MockUnicodeService()
|
||||
);
|
||||
|
||||
@@ -402,7 +402,7 @@ describe('InputHandler', () => {
|
||||
new MockLogService(),
|
||||
new MockOptionsService(),
|
||||
new MockOscLinkService(),
|
||||
new MockCoreMouseService(),
|
||||
new MockMouseStateService(),
|
||||
new MockUnicodeService()
|
||||
);
|
||||
|
||||
@@ -439,7 +439,7 @@ describe('InputHandler', () => {
|
||||
new MockLogService(),
|
||||
new MockOptionsService(),
|
||||
new MockOscLinkService(),
|
||||
new MockCoreMouseService(),
|
||||
new MockMouseStateService(),
|
||||
new MockUnicodeService()
|
||||
);
|
||||
|
||||
@@ -488,7 +488,7 @@ describe('InputHandler', () => {
|
||||
new MockLogService(),
|
||||
new MockOptionsService({ scrollOnEraseInDisplay: true }),
|
||||
new MockOscLinkService(),
|
||||
new MockCoreMouseService(),
|
||||
new MockMouseStateService(),
|
||||
new MockUnicodeService()
|
||||
);
|
||||
const aLine = Array(bufferService.cols + 1).join('a');
|
||||
@@ -520,7 +520,7 @@ describe('InputHandler', () => {
|
||||
new MockLogService(),
|
||||
new MockOptionsService(),
|
||||
new MockOscLinkService(),
|
||||
new MockCoreMouseService(),
|
||||
new MockMouseStateService(),
|
||||
new MockUnicodeService()
|
||||
);
|
||||
|
||||
@@ -646,7 +646,7 @@ describe('InputHandler', () => {
|
||||
new MockLogService(),
|
||||
new MockOptionsService(),
|
||||
new MockOscLinkService(),
|
||||
new MockCoreMouseService(),
|
||||
new MockMouseStateService(),
|
||||
new MockUnicodeService()
|
||||
);
|
||||
const container = new Uint32Array(10);
|
||||
@@ -674,7 +674,7 @@ describe('InputHandler', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
bufferService = new MockBufferService(80, 30);
|
||||
handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
|
||||
handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());
|
||||
});
|
||||
it('should handle DECSET/DECRST 47 (alt screen buffer)', async () => {
|
||||
await handler.parseP('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
|
||||
@@ -887,7 +887,7 @@ describe('InputHandler', () => {
|
||||
describe('colon notation', () => {
|
||||
let inputHandler2: TestInputHandler;
|
||||
beforeEach(() => {
|
||||
inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
|
||||
inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());
|
||||
});
|
||||
describe('should equal to semicolon', () => {
|
||||
it('CSI 38:2::50:100:150 m', async () => {
|
||||
@@ -2461,7 +2461,7 @@ describe('InputHandler', () => {
|
||||
bufferService = new BufferService(optionsService, new MockLogService());
|
||||
bufferService.resize(80, 30);
|
||||
coreService = new CoreService(bufferService, new MockLogService(), optionsService);
|
||||
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
|
||||
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());
|
||||
});
|
||||
|
||||
describe('stack limit', () => {
|
||||
@@ -2513,7 +2513,7 @@ describe('InputHandler', () => {
|
||||
coreService = new CoreService(bufferService, new MockLogService(), optionsService);
|
||||
coreService.onData(data => { console.log(data); });
|
||||
|
||||
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
|
||||
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockMouseStateService(), new MockUnicodeService());
|
||||
});
|
||||
|
||||
it('async CUP with CPR check', async () => {
|
||||
|
||||
+12
-12
@@ -15,7 +15,7 @@ import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } fr
|
||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { ICoreService, IBufferService, IOptionsService, ILogService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services';
|
||||
import { ICoreService, IBufferService, IOptionsService, ILogService, IMouseStateService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services';
|
||||
import { UnicodeService } from 'common/services/UnicodeService';
|
||||
import { OscHandler } from 'common/parser/OscParser';
|
||||
import { DcsHandler } from 'common/parser/DcsParser';
|
||||
@@ -178,7 +178,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _optionsService: IOptionsService,
|
||||
private readonly _oscLinkService: IOscLinkService,
|
||||
private readonly _coreMouseService: ICoreMouseService,
|
||||
private readonly _mouseStateService: IMouseStateService,
|
||||
private readonly _unicodeService: IUnicodeService,
|
||||
private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()
|
||||
) {
|
||||
@@ -1987,19 +1987,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';
|
||||
this._mouseStateService.activeProtocol = 'X10';
|
||||
break;
|
||||
case 1000: // vt200 mouse
|
||||
// no motion.
|
||||
this._coreMouseService.activeProtocol = 'VT200';
|
||||
this._mouseStateService.activeProtocol = 'VT200';
|
||||
break;
|
||||
case 1002: // button event mouse
|
||||
this._coreMouseService.activeProtocol = 'DRAG';
|
||||
this._mouseStateService.activeProtocol = 'DRAG';
|
||||
break;
|
||||
case 1003: // any event mouse
|
||||
// any event - sends motion events,
|
||||
// even if there is no button held down.
|
||||
this._coreMouseService.activeProtocol = 'ANY';
|
||||
this._mouseStateService.activeProtocol = 'ANY';
|
||||
break;
|
||||
case 1004: // send focusin/focusout events
|
||||
// focusin: ^[[I
|
||||
@@ -2011,13 +2011,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._logService.debug('DECSET 1005 not supported (see #2507)');
|
||||
break;
|
||||
case 1006: // sgr ext mode mouse
|
||||
this._coreMouseService.activeEncoding = 'SGR';
|
||||
this._mouseStateService.activeEncoding = 'SGR';
|
||||
break;
|
||||
case 1015: // urxvt ext mode mouse - removed in #2507
|
||||
this._logService.debug('DECSET 1015 not supported (see #2507)');
|
||||
break;
|
||||
case 1016: // sgr pixels mode mouse
|
||||
this._coreMouseService.activeEncoding = 'SGR_PIXELS';
|
||||
this._mouseStateService.activeEncoding = 'SGR_PIXELS';
|
||||
break;
|
||||
case 25: // show cursor
|
||||
this._coreService.isCursorHidden = false;
|
||||
@@ -2248,7 +2248,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
case 1000: // vt200 mouse
|
||||
case 1002: // button event mouse
|
||||
case 1003: // any event mouse
|
||||
this._coreMouseService.activeProtocol = 'NONE';
|
||||
this._mouseStateService.activeProtocol = 'NONE';
|
||||
break;
|
||||
case 1004: // send focusin/focusout events
|
||||
this._coreService.decPrivateModes.sendFocus = false;
|
||||
@@ -2257,13 +2257,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._logService.debug('DECRST 1005 not supported (see #2507)');
|
||||
break;
|
||||
case 1006: // sgr ext mode mouse
|
||||
this._coreMouseService.activeEncoding = 'DEFAULT';
|
||||
this._mouseStateService.activeEncoding = 'DEFAULT';
|
||||
break;
|
||||
case 1015: // urxvt ext mode mouse - removed in #2507
|
||||
this._logService.debug('DECRST 1015 not supported (see #2507)');
|
||||
break;
|
||||
case 1016: // sgr pixels mode mouse
|
||||
this._coreMouseService.activeEncoding = 'DEFAULT';
|
||||
this._mouseStateService.activeEncoding = 'DEFAULT';
|
||||
break;
|
||||
case 25: // hide cursor
|
||||
this._coreService.isCursorHidden = true;
|
||||
@@ -2357,7 +2357,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
|
||||
// access helpers
|
||||
const dm = this._coreService.decPrivateModes;
|
||||
const { activeProtocol: mouseProtocol, activeEncoding: mouseEncoding } = this._coreMouseService;
|
||||
const { activeProtocol: mouseProtocol, activeEncoding: mouseEncoding } = this._mouseStateService;
|
||||
const cs = this._coreService;
|
||||
const { buffers, cols } = this._bufferService;
|
||||
const { active, alt } = buffers;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, UnicodeCharProperties, UnicodeCharWidth, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService, type IBufferResizeEvent } from 'common/services/Services';
|
||||
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IMouseStateService, ICharsetService, UnicodeCharProperties, UnicodeCharWidth, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService, type IBufferResizeEvent } from 'common/services/Services';
|
||||
import { UnicodeService } from 'common/services/UnicodeService';
|
||||
import { clone } from 'common/Clone';
|
||||
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
|
||||
@@ -66,22 +66,21 @@ export class MockBufferService implements IBufferService {
|
||||
public reset(): void { }
|
||||
}
|
||||
|
||||
export class MockCoreMouseService implements ICoreMouseService {
|
||||
export class MockMouseStateService implements IMouseStateService {
|
||||
public serviceBrand: any;
|
||||
public areMouseEventsActive: boolean = false;
|
||||
public activeEncoding: string = '';
|
||||
public activeProtocol: string = '';
|
||||
public isDefaultEncoding: boolean = true;
|
||||
public isPixelEncoding: boolean = false;
|
||||
public addEncoding(name: string): void { }
|
||||
public addProtocol(name: string): void { }
|
||||
public reset(): void { }
|
||||
public triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; }
|
||||
public onProtocolChange: IEvent<CoreMouseEventType> = new Emitter<CoreMouseEventType>().event;
|
||||
public explainEvents(events: CoreMouseEventType): { [event: string]: boolean } {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public consumeWheelEvent(ev: WheelEvent, cellHeight: number, dpr: number): number {
|
||||
return 1;
|
||||
}
|
||||
public restrictMouseEvent(event: ICoreMouseEvent): boolean { return true; }
|
||||
public encodeMouseEvent(event: ICoreMouseEvent): string { return ''; }
|
||||
public setCustomWheelEventHandler(customWheelEventHandler: ((event: WheelEvent) => boolean) | undefined): void { }
|
||||
public allowCustomWheelEvent(ev: WheelEvent): boolean { return true; }
|
||||
}
|
||||
|
||||
export class MockCharsetService implements ICharsetService {
|
||||
|
||||
+6
-6
@@ -7,12 +7,12 @@ import { IDeleteEvent, IInsertEvent } from 'common/CircularList';
|
||||
import { UnderlineStyle } from 'common/buffer/Constants';
|
||||
import { IBufferSet } from 'common/buffer/Types';
|
||||
import { IParams } from 'common/parser/Types';
|
||||
import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services';
|
||||
import { IMouseStateService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services';
|
||||
import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from '@xterm/xterm';
|
||||
import type { Emitter, IEvent } from 'common/Event';
|
||||
|
||||
export interface ICoreTerminal {
|
||||
coreMouseService: ICoreMouseService;
|
||||
mouseStateService: IMouseStateService;
|
||||
coreService: ICoreService;
|
||||
optionsService: IOptionsService;
|
||||
unicodeService: IUnicodeService;
|
||||
@@ -343,7 +343,7 @@ export interface ICoreMouseEvent {
|
||||
* 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.
|
||||
* (like move + wheel), those are silently ignored by the MouseStateService.
|
||||
*/
|
||||
button: CoreMouseButton;
|
||||
action: CoreMouseAction;
|
||||
@@ -360,7 +360,7 @@ export interface ICoreMouseEvent {
|
||||
* CoreMouseEventType
|
||||
* To be reported to the browser component which events a mouse
|
||||
* protocol wants to be catched and forwarded as an ICoreMouseEvent
|
||||
* to CoreMouseService.
|
||||
* to MouseStateService.
|
||||
*/
|
||||
export const enum CoreMouseEventType {
|
||||
NONE = 0,
|
||||
@@ -378,7 +378,7 @@ export const enum CoreMouseEventType {
|
||||
|
||||
/**
|
||||
* Mouse protocol interface.
|
||||
* A mouse protocol can be registered and activated at the CoreMouseService.
|
||||
* A mouse protocol can be registered and activated at the MouseStateService.
|
||||
* `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
|
||||
@@ -391,7 +391,7 @@ export interface ICoreMouseProtocol {
|
||||
|
||||
/**
|
||||
* CoreMouseEncoding
|
||||
* The tracking encoding can be registered and activated at the CoreMouseService.
|
||||
* The tracking encoding can be registered and activated at the MouseStateService.
|
||||
* If a ICoreMouseEvent passes all procotol restrictions it will be encoded
|
||||
* with the active encoding and sent out.
|
||||
* Note: Returning an empty string will supress sending a mouse report,
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { CoreMouseService } from 'common/services/CoreMouseService';
|
||||
import { MockCoreService, MockBufferService, MockOptionsService } 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();
|
||||
const optionsService = new MockOptionsService();
|
||||
|
||||
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, optionsService);
|
||||
assert.equal(cms.activeEncoding, 'DEFAULT');
|
||||
assert.equal(cms.activeProtocol, 'NONE');
|
||||
});
|
||||
it('default protocols - NONE, X10, VT200, DRAG, ANY', () => {
|
||||
const cms = new CoreMouseService(bufferService, coreService, optionsService);
|
||||
assert.deepEqual(Object.keys((cms as any)._protocols), ['NONE', 'X10', 'VT200', 'DRAG', 'ANY']);
|
||||
});
|
||||
it('default encodings - DEFAULT, SGR', () => {
|
||||
const cms = new CoreMouseService(bufferService, coreService, optionsService);
|
||||
assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'SGR', 'SGR_PIXELS']);
|
||||
});
|
||||
it('protocol/encoding setter, reset', () => {
|
||||
const cms = new CoreMouseService(bufferService, coreService, optionsService);
|
||||
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, optionsService);
|
||||
cms.addEncoding('XYZ', (e: ICoreMouseEvent) => '');
|
||||
cms.activeEncoding = 'XYZ';
|
||||
assert.equal(cms.activeEncoding, 'XYZ');
|
||||
});
|
||||
it('addProtocol', () => {
|
||||
const cms = new CoreMouseService(bufferService, coreService, optionsService);
|
||||
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, optionsService);
|
||||
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, optionsService);
|
||||
reports = [];
|
||||
coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data);
|
||||
coreService.triggerBinaryEvent = (data: string) => reports.push(data);
|
||||
});
|
||||
it('NONE', () => {
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);
|
||||
});
|
||||
it('X10', () => {
|
||||
cms.activeProtocol = 'X10';
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);
|
||||
});
|
||||
it('VT200', () => {
|
||||
cms.activeProtocol = 'VT200';
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false);
|
||||
});
|
||||
it('DRAG', () => {
|
||||
cms.activeProtocol = 'DRAG';
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);
|
||||
});
|
||||
it('ANY', () => {
|
||||
cms.activeProtocol = 'ANY';
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 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, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.MOVE }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.UP }), false);
|
||||
// invalid coords
|
||||
assert.equal(cms.triggerMouseEvent({ col: -1, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 500, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: -1, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 500, x: 0, y: 0, 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, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
if (i > 222) {
|
||||
// supress mouse reports if we are out of addressible range (max. 222)
|
||||
assert.deepEqual(toBytes(reports.pop()), []);
|
||||
} else {
|
||||
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, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`);
|
||||
}
|
||||
});
|
||||
it('SGR_PIXELS encoding', () => {
|
||||
cms.activeProtocol = 'ANY';
|
||||
cms.activeEncoding = 'SGR_PIXELS';
|
||||
for (let i = 0; i < 500; ++i) {
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: i, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.deepEqual(reports.pop(), `\x1b[<0;${i};0M`);
|
||||
}
|
||||
});
|
||||
});
|
||||
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, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 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!!']);
|
||||
reports = [];
|
||||
|
||||
// all buttons + up + no modifier
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 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`!!']);
|
||||
reports = [];
|
||||
|
||||
// all buttons + move + no modifier
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 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!!']);
|
||||
reports = [];
|
||||
|
||||
// button none + move + modifiers
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: false, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: true }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: true, shift: false }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: true }), true);
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, x: 0, y: 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_!!']);
|
||||
reports = [];
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { MouseStateService } from 'common/services/MouseStateService';
|
||||
import { assert } from 'chai';
|
||||
import { ICoreMouseEvent, CoreMouseEventType } from 'common/Types';
|
||||
|
||||
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('MouseStateService', () => {
|
||||
it('init', () => {
|
||||
const cms = new MouseStateService();
|
||||
assert.equal(cms.activeEncoding, 'DEFAULT');
|
||||
assert.equal(cms.activeProtocol, 'NONE');
|
||||
});
|
||||
it('default protocols - NONE, X10, VT200, DRAG, ANY', () => {
|
||||
const cms = new MouseStateService();
|
||||
assert.deepEqual(Object.keys((cms as any)._protocols), ['NONE', 'X10', 'VT200', 'DRAG', 'ANY']);
|
||||
});
|
||||
it('default encodings - DEFAULT, SGR', () => {
|
||||
const cms = new MouseStateService();
|
||||
assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'SGR', 'SGR_PIXELS']);
|
||||
});
|
||||
it('protocol/encoding setter, reset', () => {
|
||||
const cms = new MouseStateService();
|
||||
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 MouseStateService();
|
||||
cms.addEncoding('XYZ', (e: ICoreMouseEvent) => '');
|
||||
cms.activeEncoding = 'XYZ';
|
||||
assert.equal(cms.activeEncoding, 'XYZ');
|
||||
});
|
||||
it('addProtocol', () => {
|
||||
const cms = new MouseStateService();
|
||||
cms.addProtocol('XYZ', { events: CoreMouseEventType.NONE, restrict: (e: ICoreMouseEvent) => false });
|
||||
cms.activeProtocol = 'XYZ';
|
||||
assert.equal(cms.activeProtocol, 'XYZ');
|
||||
});
|
||||
it('onProtocolChange', () => {
|
||||
const cms = new MouseStateService();
|
||||
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
|
||||
]);
|
||||
});
|
||||
it('restrictMouseEvent/encodeMouseEvent', () => {
|
||||
const cms = new MouseStateService();
|
||||
const event: ICoreMouseEvent = {
|
||||
col: 1,
|
||||
row: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
button: 0,
|
||||
action: 1,
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
shift: false
|
||||
};
|
||||
cms.activeProtocol = 'ANY';
|
||||
cms.activeEncoding = 'DEFAULT';
|
||||
assert.equal(cms.restrictMouseEvent(event), true);
|
||||
assert.deepEqual(toBytes(cms.encodeMouseEvent(event)), [0x1b, 0x5b, 0x4d, 0x20, 0x21, 0x21]);
|
||||
});
|
||||
});
|
||||
+20
-131
@@ -2,7 +2,7 @@
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { IBufferService, ICoreService, ICoreMouseService, IOptionsService } from 'common/services/Services';
|
||||
import { IMouseStateService } from 'common/services/Services';
|
||||
import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { Emitter } from 'common/Event';
|
||||
@@ -151,7 +151,7 @@ const DEFAULT_ENCODINGS: { [key: string]: CoreMouseEncoding } = {
|
||||
};
|
||||
|
||||
/**
|
||||
* CoreMouseService
|
||||
* MouseStateService
|
||||
*
|
||||
* Provides mouse tracking reports with different protocols and encodings.
|
||||
* - protocols: NONE (default), X10, VT200, DRAG, ANY
|
||||
@@ -166,25 +166,21 @@ const DEFAULT_ENCODINGS: { [key: string]: CoreMouseEncoding } = {
|
||||
* a tracking report to the backend based on protocol and encoding limitations.
|
||||
* To send a mouse event call `triggerMouseEvent`.
|
||||
*/
|
||||
export class CoreMouseService extends Disposable implements ICoreMouseService {
|
||||
export class MouseStateService extends Disposable implements IMouseStateService {
|
||||
public serviceBrand: any;
|
||||
|
||||
private _protocols: { [name: string]: ICoreMouseProtocol } = {};
|
||||
private _encodings: { [name: string]: CoreMouseEncoding } = {};
|
||||
private _activeProtocol: string = '';
|
||||
private _activeEncoding: string = '';
|
||||
private _lastEvent: ICoreMouseEvent | null = null;
|
||||
private _wheelPartialScroll: number = 0;
|
||||
private _customWheelEventHandler: ((event: WheelEvent) => boolean) | undefined;
|
||||
|
||||
private readonly _onProtocolChange = this._register(new Emitter<CoreMouseEventType>());
|
||||
public readonly onProtocolChange = this._onProtocolChange.event;
|
||||
|
||||
constructor(
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@ICoreService private readonly _coreService: ICoreService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// register default protocols and encodings
|
||||
for (const name of Object.keys(DEFAULT_PROTOCOLS)) this.addProtocol(name, DEFAULT_PROTOCOLS[name]);
|
||||
for (const name of Object.keys(DEFAULT_ENCODINGS)) this.addEncoding(name, DEFAULT_ENCODINGS[name]);
|
||||
@@ -230,136 +226,29 @@ export class CoreMouseService extends Disposable implements ICoreMouseService {
|
||||
public reset(): void {
|
||||
this.activeProtocol = 'NONE';
|
||||
this.activeEncoding = 'DEFAULT';
|
||||
this._lastEvent = null;
|
||||
this._wheelPartialScroll = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a wheel event, accounting for partial scrolls for trackpad, mouse scrolls.
|
||||
* This prevents hyper-sensitive scrolling in alt buffer.
|
||||
*/
|
||||
public consumeWheelEvent(ev: WheelEvent, cellHeight?: number, dpr?: number): number {
|
||||
// Do nothing if it's not a vertical scroll event
|
||||
if (ev.deltaY === 0 || ev.shiftKey) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cellHeight === undefined || dpr === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const targetWheelEventPixels = cellHeight / dpr;
|
||||
let amount = this._applyScrollModifier(ev.deltaY, ev);
|
||||
|
||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
|
||||
amount /= (targetWheelEventPixels + 0.0); // Prevent integer division
|
||||
|
||||
const isLikelyTrackpad = Math.abs(ev.deltaY) < 50;
|
||||
if (isLikelyTrackpad) {
|
||||
amount *= 0.3;
|
||||
}
|
||||
|
||||
this._wheelPartialScroll += amount;
|
||||
amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);
|
||||
this._wheelPartialScroll %= 1;
|
||||
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
||||
amount *= this._bufferService.rows;
|
||||
}
|
||||
return amount;
|
||||
public setCustomWheelEventHandler(customWheelEventHandler: ((event: WheelEvent) => boolean) | undefined): void {
|
||||
this._customWheelEventHandler = customWheelEventHandler;
|
||||
}
|
||||
|
||||
private _applyScrollModifier(amount: number, ev: WheelEvent): number {
|
||||
// Multiply the scroll speed when the modifier key is pressed
|
||||
if (ev.altKey || ev.ctrlKey || ev.shiftKey) {
|
||||
return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity;
|
||||
}
|
||||
return amount * this._optionsService.rawOptions.scrollSensitivity;
|
||||
public allowCustomWheelEvent(ev: WheelEvent): boolean {
|
||||
return this._customWheelEventHandler ? this._customWheelEventHandler(ev) !== false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 events at grid or pixel level
|
||||
if (e.action === CoreMouseAction.MOVE
|
||||
&& this._lastEvent
|
||||
&& this._equalEvents(this._lastEvent, e, this._activeEncoding === 'SGR_PIXELS')
|
||||
) {
|
||||
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);
|
||||
if (report) {
|
||||
// always send DEFAULT as binary data
|
||||
if (this._activeEncoding === 'DEFAULT') {
|
||||
this._coreService.triggerBinaryEvent(report);
|
||||
} else {
|
||||
this._coreService.triggerDataEvent(report, true);
|
||||
}
|
||||
}
|
||||
|
||||
this._lastEvent = e;
|
||||
|
||||
return true;
|
||||
public restrictMouseEvent(e: ICoreMouseEvent): boolean {
|
||||
return this._protocols[this._activeProtocol].restrict(e);
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
public encodeMouseEvent(e: ICoreMouseEvent): string {
|
||||
return this._encodings[this._activeEncoding](e);
|
||||
}
|
||||
|
||||
private _equalEvents(e1: ICoreMouseEvent, e2: ICoreMouseEvent, pixels: boolean): boolean {
|
||||
if (pixels) {
|
||||
if (e1.x !== e2.x) return false;
|
||||
if (e1.y !== e2.y) return false;
|
||||
} else {
|
||||
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;
|
||||
public get isDefaultEncoding(): boolean {
|
||||
return this._activeEncoding === 'DEFAULT';
|
||||
}
|
||||
|
||||
public get isPixelEncoding(): boolean {
|
||||
return this._activeEncoding === 'SGR_PIXELS';
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,8 @@ export interface IBufferResizeEvent {
|
||||
rowsChanged: boolean;
|
||||
}
|
||||
|
||||
export const ICoreMouseService = createDecorator<ICoreMouseService>('CoreMouseService');
|
||||
export interface ICoreMouseService {
|
||||
export const IMouseStateService = createDecorator<IMouseStateService>('MouseStateService');
|
||||
export interface IMouseStateService {
|
||||
serviceBrand: undefined;
|
||||
|
||||
activeProtocol: string;
|
||||
@@ -43,33 +43,17 @@ export interface ICoreMouseService {
|
||||
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;
|
||||
setCustomWheelEventHandler(customWheelEventHandler: ((event: WheelEvent) => boolean) | undefined): void;
|
||||
allowCustomWheelEvent(ev: WheelEvent): boolean;
|
||||
|
||||
/**
|
||||
* Event to announce changes in mouse tracking.
|
||||
*/
|
||||
onProtocolChange: IEvent<CoreMouseEventType>;
|
||||
|
||||
/**
|
||||
* Human readable version of mouse events.
|
||||
*/
|
||||
explainEvents(events: CoreMouseEventType): { [event: string]: boolean };
|
||||
|
||||
/**
|
||||
* Process wheel event taking partial scroll into account.
|
||||
*/
|
||||
consumeWheelEvent(ev: WheelEvent, cellHeight?: number, dpr?: number): number;
|
||||
restrictMouseEvent(event: ICoreMouseEvent): boolean;
|
||||
encodeMouseEvent(event: ICoreMouseEvent): string;
|
||||
readonly isDefaultEncoding: boolean;
|
||||
readonly isPixelEncoding: boolean;
|
||||
}
|
||||
|
||||
export const ICoreService = createDecorator<ICoreService>('CoreService');
|
||||
|
||||
@@ -103,7 +103,7 @@ export class Terminal extends Disposable implements ITerminalApi {
|
||||
public get modes(): IModes {
|
||||
const m = this._core.coreService.decPrivateModes;
|
||||
let mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any' = 'none';
|
||||
switch (this._core.coreMouseService.activeProtocol) {
|
||||
switch (this._core.mouseStateService.activeProtocol) {
|
||||
case 'X10': mouseTrackingMode = 'x10'; break;
|
||||
case 'VT200': mouseTrackingMode = 'vt200'; break;
|
||||
case 'DRAG': mouseTrackingMode = 'drag'; break;
|
||||
|
||||
@@ -584,13 +584,13 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
});
|
||||
test('Ps = 1 0 0 5 - Enable UTF-8 Mouse Mode, xterm', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');
|
||||
await ctx.proxy.write('\x1b[?1005h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');
|
||||
});
|
||||
test('Ps = 1 0 0 6 - Enable SGR Mouse Mode, xterm', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');
|
||||
});
|
||||
test('Ps = 1 0 0 7 - Enable Alternate Scroll Mode, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1007h');
|
||||
@@ -603,13 +603,13 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
});
|
||||
test('Ps = 1 0 1 5 - Enable urxvt Mouse Mode', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');
|
||||
await ctx.proxy.write('\x1b[?1015h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');
|
||||
});
|
||||
test('Ps = 1 0 1 6 - Enable SGR Mouse PixelMode, xterm', async () => {
|
||||
await ctx.proxy.write('\x1b[?1016h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR_PIXELS');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR_PIXELS');
|
||||
});
|
||||
test('Ps = 1 0 3 4 - Interpret "meta" key, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1034h');
|
||||
@@ -916,15 +916,15 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
});
|
||||
test('Ps = 1 0 0 5 - Disable UTF-8 Mouse Mode, xterm.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');
|
||||
await ctx.proxy.write('\x1b[?1005l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');
|
||||
});
|
||||
test('Ps = 1 0 0 6 - Disable SGR Mouse Mode, xterm.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');
|
||||
await ctx.proxy.write('\x1b[?1006l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'DEFAULT');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'DEFAULT');
|
||||
});
|
||||
test('Ps = 1 0 0 7 - Disable Alternate Scroll Mode, xterm. This corresponds to the alternateScroll resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1007l');
|
||||
@@ -937,15 +937,15 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
});
|
||||
test('Ps = 1 0 1 5 - Disable urxvt Mouse Mode.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');
|
||||
await ctx.proxy.write('\x1b[?1015l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR');
|
||||
});
|
||||
test('Ps = 1 0 1 6 - Disable SGR Mouse Pixel-Mode, xterm.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1016h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR_PIXELS');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'SGR_PIXELS');
|
||||
await ctx.proxy.write('\x1b[?1016l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'DEFAULT');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding), 'DEFAULT');
|
||||
});
|
||||
test('Ps = 1 0 3 4 - Don\'t interpret "meta" key, xterm. This disables the eightBitInput resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1034l');
|
||||
@@ -1849,8 +1849,8 @@ async function getModeSnapshot(): Promise<any> {
|
||||
cols: await ctx.proxy.cols,
|
||||
rows: await ctx.proxy.rows,
|
||||
bufferType: await ctx.proxy.buffer.active.type,
|
||||
mouseProtocol: await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeProtocol),
|
||||
mouseEncoding: await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding)
|
||||
mouseProtocol: await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeProtocol),
|
||||
mouseEncoding: await ctx.proxy.core.evaluate(([core]) => core.mouseStateService.activeEncoding)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user