Move non-state methods into MouseService

Fixes #5756
This commit is contained in:
Daniel Imms
2026-03-11 10:49:55 +09:00
parent d63445be94
commit 5f3a4646be
8 changed files with 190 additions and 327 deletions
+1
View File
@@ -1095,6 +1095,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
this._setup();
super.reset();
this._mouseService?.reset();
this._selectionService?.reset();
this._decorationService.reset();
+2 -3
View File
@@ -393,9 +393,8 @@ export class MockMouseService implements IMouseService {
throw new Error('Not implemented');
}
public bindMouse(): void {
throw new Error('Not implemented');
}
public bindMouse(): void { }
public reset(): void { }
}
export class MockRenderService implements IRenderService {
+140 -5
View File
@@ -5,7 +5,7 @@
import { addDisposableListener } from 'browser/Dom';
import { IBufferService, IMouseStateService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
import { CoreMouseAction, CoreMouseButton, CoreMouseEventType, IDisposable } from 'common/Types';
import { CoreMouseAction, CoreMouseButton, CoreMouseEventType, ICoreMouseEvent, IDisposable } from 'common/Types';
import { C0 } from 'common/data/EscapeSequences';
import { toDisposable } from 'common/Lifecycle';
import { ICoreBrowserService, IMouseCoordsService, IMouseService, IMouseServiceTarget, IRenderService, ISelectionService } from './Services';
@@ -21,6 +21,9 @@ interface IMouseBindContext {
export class MouseService implements IMouseService {
public serviceBrand: undefined;
private _lastEvent: ICoreMouseEvent | null = null;
private _wheelPartialScroll: number = 0;
constructor(
@IRenderService private readonly _renderService: IRenderService,
@IMouseCoordsService private readonly _mouseCoordsService: IMouseCoordsService,
@@ -123,7 +126,7 @@ export class MouseService implements IMouseService {
if (deltaY === 0) {
return false;
}
const lines = this._mouseStateService.consumeWheelEvent(
const lines = this._consumeWheelEvent(
ev as WheelEvent,
this._renderService?.dimensions?.device?.cell?.height,
this._coreBrowserService?.dpr
@@ -145,7 +148,7 @@ export class MouseService implements IMouseService {
return false;
}
return this._mouseStateService.triggerMouseEvent({
return this._triggerMouseEvent({
col: pos.col,
row: pos.row,
x: pos.x,
@@ -241,7 +244,7 @@ export class MouseService implements IMouseService {
return false;
}
const lines = this._mouseStateService.consumeWheelEvent(
const lines = this._consumeWheelEvent(
ev,
this._renderService?.dimensions?.device?.cell?.height,
this._coreBrowserService?.dpr
@@ -261,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._mouseStateService.explainEvents(events));
this._logService.debug('Binding to mouse events:', this._explainEvents(events));
}
element.classList.add('enable-mouse-events');
this._selectionService.disable();
@@ -317,4 +325,131 @@ export class MouseService implements IMouseService {
}
}
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;
}
}
+1
View File
@@ -62,6 +62,7 @@ export interface IMouseService {
serviceBrand: undefined;
bindMouse(target: IMouseServiceTarget, register: (disposable: IDisposable) => void, focus: () => void): void;
reset(): void;
}
export interface IMouseServiceTarget {
element: HTMLElement;