Skip to content

Commit 5f3a464

Browse files
committed
Move non-state methods into MouseService
Fixes #5756
1 parent d63445b commit 5f3a464

File tree

8 files changed

+190
-327
lines changed

8 files changed

+190
-327
lines changed

src/browser/CoreBrowserTerminal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
10951095

10961096
this._setup();
10971097
super.reset();
1098+
this._mouseService?.reset();
10981099
this._selectionService?.reset();
10991100
this._decorationService.reset();
11001101

src/browser/TestUtils.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,8 @@ export class MockMouseService implements IMouseService {
393393
throw new Error('Not implemented');
394394
}
395395

396-
public bindMouse(): void {
397-
throw new Error('Not implemented');
398-
}
396+
public bindMouse(): void { }
397+
public reset(): void { }
399398
}
400399

401400
export class MockRenderService implements IRenderService {

src/browser/services/MouseService.ts

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { addDisposableListener } from 'browser/Dom';
77
import { IBufferService, IMouseStateService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
8-
import { CoreMouseAction, CoreMouseButton, CoreMouseEventType, IDisposable } from 'common/Types';
8+
import { CoreMouseAction, CoreMouseButton, CoreMouseEventType, ICoreMouseEvent, IDisposable } from 'common/Types';
99
import { C0 } from 'common/data/EscapeSequences';
1010
import { toDisposable } from 'common/Lifecycle';
1111
import { ICoreBrowserService, IMouseCoordsService, IMouseService, IMouseServiceTarget, IRenderService, ISelectionService } from './Services';
@@ -21,6 +21,9 @@ interface IMouseBindContext {
2121
export class MouseService implements IMouseService {
2222
public serviceBrand: undefined;
2323

24+
private _lastEvent: ICoreMouseEvent | null = null;
25+
private _wheelPartialScroll: number = 0;
26+
2427
constructor(
2528
@IRenderService private readonly _renderService: IRenderService,
2629
@IMouseCoordsService private readonly _mouseCoordsService: IMouseCoordsService,
@@ -123,7 +126,7 @@ export class MouseService implements IMouseService {
123126
if (deltaY === 0) {
124127
return false;
125128
}
126-
const lines = this._mouseStateService.consumeWheelEvent(
129+
const lines = this._consumeWheelEvent(
127130
ev as WheelEvent,
128131
this._renderService?.dimensions?.device?.cell?.height,
129132
this._coreBrowserService?.dpr
@@ -145,7 +148,7 @@ export class MouseService implements IMouseService {
145148
return false;
146149
}
147150

148-
return this._mouseStateService.triggerMouseEvent({
151+
return this._triggerMouseEvent({
149152
col: pos.col,
150153
row: pos.row,
151154
x: pos.x,
@@ -241,7 +244,7 @@ export class MouseService implements IMouseService {
241244
return false;
242245
}
243246

244-
const lines = this._mouseStateService.consumeWheelEvent(
247+
const lines = this._consumeWheelEvent(
245248
ev,
246249
this._renderService?.dimensions?.device?.cell?.height,
247250
this._coreBrowserService?.dpr
@@ -261,13 +264,18 @@ export class MouseService implements IMouseService {
261264
}
262265
}
263266

267+
public reset(): void {
268+
this._lastEvent = null;
269+
this._wheelPartialScroll = 0;
270+
}
271+
264272
private _handleProtocolChange(ctx: IMouseBindContext, eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener>, events: CoreMouseEventType): void {
265273
const { element, document } = ctx.target;
266274
const { requestedEvents } = ctx;
267275
// apply global changes on events
268276
if (events) {
269277
if (this._optionsService.rawOptions.logLevel === 'debug') {
270-
this._logService.debug('Binding to mouse events:', this._mouseStateService.explainEvents(events));
278+
this._logService.debug('Binding to mouse events:', this._explainEvents(events));
271279
}
272280
element.classList.add('enable-mouse-events');
273281
this._selectionService.disable();
@@ -317,4 +325,131 @@ export class MouseService implements IMouseService {
317325
}
318326
}
319327

328+
private _applyScrollModifier(amount: number, ev: WheelEvent): number {
329+
// Multiply the scroll speed when the modifier key is pressed
330+
if (ev.altKey || ev.ctrlKey || ev.shiftKey) {
331+
return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity;
332+
}
333+
return amount * this._optionsService.rawOptions.scrollSensitivity;
334+
}
335+
336+
/**
337+
* Processes a wheel event, accounting for partial scrolls for trackpad, mouse scrolls.
338+
* This prevents hyper-sensitive scrolling in alt buffer.
339+
*/
340+
private _consumeWheelEvent(ev: WheelEvent, cellHeight?: number, dpr?: number): number {
341+
// Do nothing if it's not a vertical scroll event
342+
if (ev.deltaY === 0 || ev.shiftKey) {
343+
return 0;
344+
}
345+
346+
if (cellHeight === undefined || dpr === undefined) {
347+
return 0;
348+
}
349+
350+
const targetWheelEventPixels = cellHeight / dpr;
351+
let amount = this._applyScrollModifier(ev.deltaY, ev);
352+
353+
if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
354+
amount /= (targetWheelEventPixels + 0.0); // Prevent integer division
355+
356+
const isLikelyTrackpad = Math.abs(ev.deltaY) < 50;
357+
if (isLikelyTrackpad) {
358+
amount *= 0.3;
359+
}
360+
361+
this._wheelPartialScroll += amount;
362+
amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);
363+
this._wheelPartialScroll %= 1;
364+
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
365+
amount *= this._bufferService.rows;
366+
}
367+
return amount;
368+
}
369+
370+
/**
371+
* Triggers a mouse event to be sent.
372+
*
373+
* Returns true if the event passed all protocol restrictions and a report
374+
* was sent, otherwise false. The return value may be used to decide whether
375+
* the default event action in the browser component should be omitted.
376+
*
377+
* Note: The method will change values of the given event object
378+
* to fulfill protocol and encoding restrictions.
379+
*/
380+
private _triggerMouseEvent(e: ICoreMouseEvent): boolean {
381+
// range check for col/row
382+
if (e.col < 0 || e.col >= this._bufferService.cols
383+
|| e.row < 0 || e.row >= this._bufferService.rows) {
384+
return false;
385+
}
386+
387+
// filter nonsense combinations of button + action
388+
if (e.button === CoreMouseButton.WHEEL && e.action === CoreMouseAction.MOVE) {
389+
return false;
390+
}
391+
if (e.button === CoreMouseButton.NONE && e.action !== CoreMouseAction.MOVE) {
392+
return false;
393+
}
394+
if (e.button !== CoreMouseButton.WHEEL && (e.action === CoreMouseAction.LEFT || e.action === CoreMouseAction.RIGHT)) {
395+
return false;
396+
}
397+
398+
// report 1-based coords
399+
e.col++;
400+
e.row++;
401+
402+
// debounce move events at grid or pixel level
403+
if (e.action === CoreMouseAction.MOVE
404+
&& this._lastEvent
405+
&& this._equalEvents(this._lastEvent, e, this._mouseStateService.isPixelEncoding)
406+
) {
407+
return false;
408+
}
409+
410+
// apply protocol restrictions
411+
if (!this._mouseStateService.restrictMouseEvent(e)) {
412+
return false;
413+
}
414+
415+
// encode report and send
416+
const report = this._mouseStateService.encodeMouseEvent(e);
417+
if (report) {
418+
if (this._mouseStateService.isDefaultEncoding) {
419+
this._coreService.triggerBinaryEvent(report);
420+
} else {
421+
this._coreService.triggerDataEvent(report, true);
422+
}
423+
}
424+
425+
this._lastEvent = e;
426+
return true;
427+
}
428+
429+
private _explainEvents(events: CoreMouseEventType): { [event: string]: boolean } {
430+
return {
431+
down: !!(events & CoreMouseEventType.DOWN),
432+
up: !!(events & CoreMouseEventType.UP),
433+
drag: !!(events & CoreMouseEventType.DRAG),
434+
move: !!(events & CoreMouseEventType.MOVE),
435+
wheel: !!(events & CoreMouseEventType.WHEEL)
436+
};
437+
}
438+
439+
private _equalEvents(e1: ICoreMouseEvent, e2: ICoreMouseEvent, pixels: boolean): boolean {
440+
if (pixels) {
441+
if (e1.x !== e2.x) return false;
442+
if (e1.y !== e2.y) return false;
443+
} else {
444+
if (e1.col !== e2.col) return false;
445+
if (e1.row !== e2.row) return false;
446+
}
447+
if (e1.button !== e2.button) return false;
448+
if (e1.action !== e2.action) return false;
449+
if (e1.ctrl !== e2.ctrl) return false;
450+
if (e1.alt !== e2.alt) return false;
451+
if (e1.shift !== e2.shift) return false;
452+
return true;
453+
}
454+
320455
}

src/browser/services/Services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export interface IMouseService {
6262
serviceBrand: undefined;
6363

6464
bindMouse(target: IMouseServiceTarget, register: (disposable: IDisposable) => void, focus: () => void): void;
65+
reset(): void;
6566
}
6667
export interface IMouseServiceTarget {
6768
element: HTMLElement;

src/common/TestUtils.test.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,14 @@ export class MockMouseStateService implements IMouseStateService {
7171
public areMouseEventsActive: boolean = false;
7272
public activeEncoding: string = '';
7373
public activeProtocol: string = '';
74+
public isDefaultEncoding: boolean = true;
75+
public isPixelEncoding: boolean = false;
7476
public addEncoding(name: string): void { }
7577
public addProtocol(name: string): void { }
7678
public reset(): void { }
77-
public triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; }
7879
public onProtocolChange: IEvent<CoreMouseEventType> = new Emitter<CoreMouseEventType>().event;
79-
public explainEvents(events: CoreMouseEventType): { [event: string]: boolean } {
80-
throw new Error('Method not implemented.');
81-
}
82-
public consumeWheelEvent(ev: WheelEvent, cellHeight: number, dpr: number): number {
83-
return 1;
84-
}
80+
public restrictMouseEvent(event: ICoreMouseEvent): boolean { return true; }
81+
public encodeMouseEvent(event: ICoreMouseEvent): string { return ''; }
8582
public setCustomWheelEventHandler(customWheelEventHandler: ((event: WheelEvent) => boolean) | undefined): void { }
8683
public allowCustomWheelEvent(ev: WheelEvent): boolean { return true; }
8784
}

0 commit comments

Comments
 (0)