Fix mouseEventsRequireAlt by stripping alt from PTY mouse reports.

Alt is only used locally to gate passthrough; applications like tmux ignore alt-modified SGR sequences.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Daniel Imms
2026-05-27 16:11:35 -07:00
co-authored by Cursor
parent 7711d780c2
commit f8e9a81d0e
4 changed files with 53 additions and 3 deletions
+42
View File
@@ -290,4 +290,46 @@ describe('MouseService mouseEventsRequireAlt', () => {
assert.isTrue(element.classList.contains('enable-mouse-events'));
assert.equal(selectionService.disableCount, 2);
});
it('should strip alt modifier from forwarded mouse reports', () => {
const mouseStateService = new MouseStateService();
mouseStateService.activeProtocol = 'ANY';
mouseStateService.activeEncoding = 'SGR';
const optionsService = new OptionsService({ mouseEventsRequireAlt: true });
const reports: string[] = [];
const mouseService = new MouseService(
new MockRenderService(),
{
getMouseReportCoords: () => ({ col: 0, row: 0, x: 0, y: 0 })
} as any,
mouseStateService,
{
triggerDataEvent: (data: string) => reports.push(data),
triggerBinaryEvent: () => {},
decPrivateModes: { applicationCursorKeys: false }
} as any,
bufferService,
optionsService,
new TestSelectionService(),
logService,
new MockCoreBrowserService()
);
const ctx = {
target: {
screenElement: createTestMouseTargetElement()
},
requestedEvents: {}
} as any;
const sent = (mouseService as any)._sendEvent(ctx, {
type: 'mousedown',
button: 0,
altKey: true,
ctrlKey: false,
shiftKey: false
} as MouseEvent);
assert.isTrue(sent);
assert.deepEqual(reports, ['\x1b[<0;1;1M']);
});
});
+7 -1
View File
@@ -163,6 +163,12 @@ export class MouseService implements IMouseService {
return false;
}
// Alt is only used locally to gate mouse passthrough; do not forward it to the
// application (e.g. tmux ignores alt-modified mouse reports).
const stripAltFromReport = but !== CoreMouseButton.WHEEL
&& this._optionsService.rawOptions.mouseEventsRequireAlt
&& this._mouseStateService.areMouseEventsActive;
return this._triggerMouseEvent({
col: pos.col,
row: pos.row,
@@ -171,7 +177,7 @@ export class MouseService implements IMouseService {
button: but,
action,
ctrl: ev.ctrlKey,
alt: ev.altKey,
alt: stripAltFromReport ? false : ev.altKey,
shift: ev.shiftKey
});
}
+2 -1
View File
@@ -139,7 +139,8 @@ declare module '@xterm/headless' {
/**
* When enabled and the terminal is in mouse events mode, mouse click, drag,
* and move events are only sent to the underlying application when the alt
* key is held. Wheel events are not affected. This allows normal text
* key is held. The alt key is not included in the mouse reports sent to the
* application. Wheel events are not affected. This allows normal text
* selection by default while still supporting application mouse interaction
* and scrolling when holding alt. When enabled, this takes precedence over
* `macOptionClickForcesSelection`.
+2 -1
View File
@@ -209,7 +209,8 @@ declare module '@xterm/xterm' {
/**
* When enabled and the terminal is in mouse events mode, mouse click, drag,
* and move events are only sent to the underlying application when the alt
* key is held. Wheel events are not affected. This allows normal text
* key is held. The alt key is not included in the mouse reports sent to the
* application. Wheel events are not affected. This allows normal text
* selection by default while still supporting application mouse interaction
* and scrolling when holding alt. When enabled, this takes precedence over
* `macOptionClickForcesSelection`.