Toggle enable-mouse-events cursor while alt is held for mouseEventsRequireAlt.

Extract AltMouseCursorController to bind DOM listeners only when the option and mouse protocol are active.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Daniel Imms
2026-05-27 16:20:45 -07:00
co-authored by Cursor
parent f8e9a81d0e
commit bf8af4a871
2 changed files with 92 additions and 3 deletions
+73 -2
View File
@@ -7,7 +7,7 @@ import { addDisposableListener } from 'browser/Dom';
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 { DisposableStore, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { ICoreBrowserService, IMouseCoordsService, IMouseService, IMouseServiceTarget, IRenderService, ISelectionService } from './Services';
import { Gesture, EventType as GestureEventType, IGestureEvent } from 'browser/scrollable/touch';
@@ -25,6 +25,7 @@ export class MouseService implements IMouseService {
private _lastEvent: ICoreMouseEvent | null = null;
private _wheelPartialScroll: number = 0;
private _touchScrollAccumulator: number = 0;
private _altMouseCursor: AltMouseCursorController | undefined;
constructor(
@IRenderService private readonly _renderService: IRenderService,
@@ -63,11 +64,19 @@ export class MouseService implements IMouseService {
mousedrag: (ev: Event) => this._handleMouseDrag(ctx, ev as MouseEvent),
mousemove: (ev: Event) => this._handleMouseMove(ctx, ev as MouseEvent)
};
this._altMouseCursor = new AltMouseCursorController(
element,
document,
() => this._mouseStateService.areMouseEventsActive
&& !!this._optionsService.rawOptions.mouseEventsRequireAlt
);
register(this._altMouseCursor);
register(this._mouseStateService.onProtocolChange(events => {
this._handleProtocolChange(ctx, eventListeners, events);
}));
register(this._optionsService.onSpecificOptionChange('mouseEventsRequireAlt', () => {
this._syncMouseModeState(element);
this._altMouseCursor?.sync();
}));
// force initial onProtocolChange so we dont miss early mouse requests
this._mouseStateService.activeProtocol = this._mouseStateService.activeProtocol;
@@ -372,7 +381,7 @@ export class MouseService implements IMouseService {
private _syncMouseModeState(element: HTMLElement): void {
if (this._mouseStateService.areMouseEventsActive) {
if (this._optionsService.rawOptions.mouseEventsRequireAlt) {
element.classList.remove('enable-mouse-events');
this._altMouseCursor?.resetClass();
this._selectionService.enable();
} else {
element.classList.add('enable-mouse-events');
@@ -396,6 +405,7 @@ export class MouseService implements IMouseService {
this._logService.debug('Unbinding from mouse events.');
}
this._syncMouseModeState(element);
this._altMouseCursor?.sync();
// add/remove handlers from requestedEvents
if (!(events & CoreMouseEventType.MOVE)) {
@@ -565,3 +575,64 @@ export class MouseService implements IMouseService {
}
}
/**
* Toggles `enable-mouse-events` on the terminal element while alt is held when
* `mouseEventsRequireAlt` is active. DOM listeners are only registered while active.
*/
export class AltMouseCursorController implements IDisposable {
private readonly _listeners = new MutableDisposable<IDisposable>();
constructor(
private readonly _element: HTMLElement,
private readonly _document: Document,
private readonly _isActive: () => boolean
) {
}
public dispose(): void {
this._listeners.dispose();
}
public sync(): void {
this._listeners.clear();
if (!this._isActive()) {
return;
}
const store = new DisposableStore();
const syncFromModifier = (ev: KeyboardEvent | MouseEvent) => this.syncFromModifier(ev);
store.add(addDisposableListener(this._document, 'keydown', syncFromModifier));
store.add(addDisposableListener(this._document, 'keyup', syncFromModifier));
store.add(addDisposableListener(this._element, 'mousemove', syncFromModifier));
const targetWindow = this._element.ownerDocument?.defaultView;
if (targetWindow) {
store.add(addDisposableListener(targetWindow, 'blur', () => {
if (this._isActive()) {
this.resetClass();
}
}));
}
this._listeners.value = store;
}
public resetClass(): void {
this._updateClass(false);
}
public syncFromModifier(ev: KeyboardEvent | MouseEvent): void {
if (!this._isActive()) {
return;
}
this._updateClass(ev.getModifierState('Alt'));
}
private _updateClass(altHeld: boolean): void {
if (altHeld) {
this._element.classList.add('enable-mouse-events');
} else {
this._element.classList.remove('enable-mouse-events');
}
}
}