From 7711d780c28923dd69578deb793fbb174d90c5d2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 27 May 2026 21:59:23 +0000 Subject: [PATCH] Handle mouseEventsRequireAlt option changes in mouse mode Co-authored-by: Daniel Imms --- src/browser/CoreBrowserTerminal.ts | 1 + src/browser/services/MouseService.test.ts | 80 +++++++++++++++++++++++ src/browser/services/MouseService.ts | 27 +++++--- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index 2942b3952..a75bd5165 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -623,6 +623,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { this.element.classList.add('enable-mouse-events'); } else { this._selectionService.enable(); + this.element.classList.remove('enable-mouse-events'); } if (this.options.screenReaderMode) { diff --git a/src/browser/services/MouseService.test.ts b/src/browser/services/MouseService.test.ts index f395083e3..e9be7477c 100644 --- a/src/browser/services/MouseService.test.ts +++ b/src/browser/services/MouseService.test.ts @@ -7,6 +7,7 @@ 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 { OptionsService } from 'common/services/OptionsService'; import { MockCoreBrowserService, MockRenderService, MockSelectionService } from 'browser/TestUtils.test'; function toBytes(s: string | undefined): number[] { @@ -42,6 +43,36 @@ const logService: ILogService = { error: () => {} } as any; +class TestSelectionService extends MockSelectionService { + public enableCount = 0; + public disableCount = 0; + + public override enable(): void { + this.enableCount++; + } + + public override disable(): void { + this.disableCount++; + } + + public override shouldForceSelection(): boolean { + return false; + } +} + +function createTestMouseTargetElement(): HTMLElement { + const classes = new Set(); + return { + classList: { + add: (className: string) => classes.add(className), + remove: (className: string) => classes.delete(className), + contains: (className: string) => classes.has(className) + }, + addEventListener: () => {}, + removeEventListener: () => {} + } as any; +} + describe('MouseService _triggerMouseEvent', () => { let mouseService: MouseService; let mouseStateService: MouseStateService; @@ -211,3 +242,52 @@ describe('MouseService _triggerMouseEvent', () => { reports = []; }); }); + +describe('MouseService mouseEventsRequireAlt', () => { + it('should update selection state and cursor class when toggled while mouse events are active', () => { + const mouseStateService = new MouseStateService(); + const optionsService = new OptionsService({}); + const selectionService = new TestSelectionService(); + const mouseService = new MouseService( + new MockRenderService(), + { + getMouseReportCoords: (_ev: MouseEvent, _el: HTMLElement) => ({ col: 0, row: 0, x: 0, y: 0 }) + } as any, + mouseStateService, + { + triggerDataEvent: () => {}, + triggerBinaryEvent: () => {}, + decPrivateModes: { applicationCursorKeys: false } + } as any, + bufferService, + optionsService, + selectionService, + logService, + new MockCoreBrowserService() + ); + const element = createTestMouseTargetElement(); + const screenElement = createTestMouseTargetElement(); + const document = { + addEventListener: () => {}, + removeEventListener: () => {} + } as any; + + mouseService.bindMouse({ + element, + screenElement, + document + }, disposable => disposable, () => {}); + + mouseStateService.activeProtocol = 'ANY'; + assert.isTrue(element.classList.contains('enable-mouse-events')); + assert.equal(selectionService.disableCount, 1); + + optionsService.options.mouseEventsRequireAlt = true; + assert.isFalse(element.classList.contains('enable-mouse-events')); + assert.equal(selectionService.enableCount, 2); + + optionsService.options.mouseEventsRequireAlt = false; + assert.isTrue(element.classList.contains('enable-mouse-events')); + assert.equal(selectionService.disableCount, 2); + }); +}); diff --git a/src/browser/services/MouseService.ts b/src/browser/services/MouseService.ts index 20f5602e6..3611890d3 100644 --- a/src/browser/services/MouseService.ts +++ b/src/browser/services/MouseService.ts @@ -66,6 +66,9 @@ export class MouseService implements IMouseService { register(this._mouseStateService.onProtocolChange(events => { this._handleProtocolChange(ctx, eventListeners, events); })); + register(this._optionsService.onSpecificOptionChange('mouseEventsRequireAlt', () => { + this._syncMouseModeState(element); + })); // force initial onProtocolChange so we dont miss early mouse requests this._mouseStateService.activeProtocol = this._mouseStateService.activeProtocol; @@ -360,6 +363,21 @@ export class MouseService implements IMouseService { this._touchScrollAccumulator = 0; } + private _syncMouseModeState(element: HTMLElement): void { + if (this._mouseStateService.areMouseEventsActive) { + if (this._optionsService.rawOptions.mouseEventsRequireAlt) { + element.classList.remove('enable-mouse-events'); + this._selectionService.enable(); + } else { + element.classList.add('enable-mouse-events'); + this._selectionService.disable(); + } + } else { + element.classList.remove('enable-mouse-events'); + this._selectionService.enable(); + } + } + private _handleProtocolChange(ctx: IMouseBindContext, eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener>, events: CoreMouseEventType): void { const { element, document } = ctx.target; const { requestedEvents } = ctx; @@ -368,17 +386,10 @@ export class MouseService implements IMouseService { if (this._optionsService.rawOptions.logLevel === 'debug') { this._logService.debug('Binding to mouse events:', this._explainEvents(events)); } - if (this._optionsService.rawOptions.mouseEventsRequireAlt) { - this._selectionService.enable(); - } else { - element.classList.add('enable-mouse-events'); - this._selectionService.disable(); - } } else { this._logService.debug('Unbinding from mouse events.'); - element.classList.remove('enable-mouse-events'); - this._selectionService.enable(); } + this._syncMouseModeState(element); // add/remove handlers from requestedEvents if (!(events & CoreMouseEventType.MOVE)) {