@xterm/xterm 6.1.0-beta.254 → 6.1.0-beta.256

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xterm/xterm",
3
3
  "description": "Full xterm terminal, in your browser",
4
- "version": "6.1.0-beta.254",
4
+ "version": "6.1.0-beta.256",
5
5
  "main": "lib/xterm.js",
6
6
  "module": "lib/xterm.mjs",
7
7
  "style": "css/xterm.css",
@@ -112,5 +112,5 @@
112
112
  "ws": "^8.20.0",
113
113
  "xterm-benchmark": "^0.3.2"
114
114
  },
115
- "commit": "00e54532b3b1b131bac8400deddb725b654c106d"
115
+ "commit": "43e8365f0e20402ce9744e3770a97dd7a78ef877"
116
116
  }
@@ -37,7 +37,7 @@ import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'
37
37
  import { CoreBrowserService } from 'browser/services/CoreBrowserService';
38
38
  import { LinkProviderService } from 'browser/services/LinkProviderService';
39
39
  import { MouseCoordsService } from 'browser/services/MouseCoordsService';
40
- import { MouseService } from 'browser/services/MouseService';
40
+ import { MouseEventCssClasses, MouseService } from 'browser/services/MouseService';
41
41
  import { RenderService } from 'browser/services/RenderService';
42
42
  import { SelectionService } from 'browser/services/SelectionService';
43
43
  import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseCoordsService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
@@ -618,11 +618,12 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
618
618
  this._register(addDisposableListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
619
619
 
620
620
  // apply mouse event classes set by escape codes before terminal was attached
621
- if (this.mouseStateService.areMouseEventsActive) {
621
+ if (this.mouseStateService.areMouseEventsActive && !this.options.mouseEventsRequireAlt) {
622
622
  this._selectionService.disable();
623
- this.element.classList.add('enable-mouse-events');
623
+ this.element.classList.add(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
624
624
  } else {
625
625
  this._selectionService.enable();
626
+ this.element.classList.remove(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
626
627
  }
627
628
 
628
629
  if (this.options.screenReaderMode) {
@@ -7,12 +7,16 @@ import { addDisposableListener } from 'browser/Dom';
7
7
  import { IBufferService, IMouseStateService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
8
8
  import { CoreMouseAction, CoreMouseButton, CoreMouseEventType, ICoreMouseEvent, IDisposable } from 'common/Types';
9
9
  import { C0 } from 'common/data/EscapeSequences';
10
- import { toDisposable } from 'common/Lifecycle';
10
+ import { DisposableStore, MutableDisposable, toDisposable } from 'common/Lifecycle';
11
11
  import { ICoreBrowserService, IMouseCoordsService, IMouseService, IMouseServiceTarget, IRenderService, ISelectionService } from './Services';
12
12
  import { Gesture, EventType as GestureEventType, IGestureEvent } from 'browser/scrollable/touch';
13
13
 
14
14
  type RequestedMouseEvents = Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener | null>;
15
15
 
16
+ export const enum MouseEventCssClasses {
17
+ ENABLE_MOUSE_EVENTS = 'enable-mouse-events'
18
+ }
19
+
16
20
  interface IMouseBindContext {
17
21
  readonly target: IMouseServiceTarget;
18
22
  readonly focus: () => void;
@@ -25,6 +29,7 @@ export class MouseService implements IMouseService {
25
29
  private _lastEvent: ICoreMouseEvent | null = null;
26
30
  private _wheelPartialScroll: number = 0;
27
31
  private _touchScrollAccumulator: number = 0;
32
+ private _altMouseCursor: AltMouseCursorController | undefined;
28
33
 
29
34
  constructor(
30
35
  @IRenderService private readonly _renderService: IRenderService,
@@ -63,9 +68,20 @@ export class MouseService implements IMouseService {
63
68
  mousedrag: (ev: Event) => this._handleMouseDrag(ctx, ev as MouseEvent),
64
69
  mousemove: (ev: Event) => this._handleMouseMove(ctx, ev as MouseEvent)
65
70
  };
71
+ this._altMouseCursor = new AltMouseCursorController(
72
+ element,
73
+ document,
74
+ () => this._mouseStateService.areMouseEventsActive
75
+ && !!this._optionsService.rawOptions.mouseEventsRequireAlt
76
+ );
77
+ register(this._altMouseCursor);
66
78
  register(this._mouseStateService.onProtocolChange(events => {
67
79
  this._handleProtocolChange(ctx, eventListeners, events);
68
80
  }));
81
+ register(this._optionsService.onSpecificOptionChange('mouseEventsRequireAlt', () => {
82
+ this._syncMouseModeState(element);
83
+ this._altMouseCursor?.sync();
84
+ }));
69
85
  // force initial onProtocolChange so we dont miss early mouse requests
70
86
  this._mouseStateService.activeProtocol = this._mouseStateService.activeProtocol;
71
87
 
@@ -153,6 +169,19 @@ export class MouseService implements IMouseService {
153
169
  return false;
154
170
  }
155
171
 
172
+ if (but !== CoreMouseButton.WHEEL
173
+ && this._optionsService.rawOptions.mouseEventsRequireAlt
174
+ && this._mouseStateService.areMouseEventsActive
175
+ && !ev.altKey) {
176
+ return false;
177
+ }
178
+
179
+ // Alt is only used locally to gate mouse passthrough; do not forward it to the
180
+ // application (e.g. tmux ignores alt-modified mouse reports).
181
+ const stripAltFromReport = but !== CoreMouseButton.WHEEL
182
+ && this._optionsService.rawOptions.mouseEventsRequireAlt
183
+ && this._mouseStateService.areMouseEventsActive;
184
+
156
185
  return this._triggerMouseEvent({
157
186
  col: pos.col,
158
187
  row: pos.row,
@@ -161,7 +190,7 @@ export class MouseService implements IMouseService {
161
190
  button: but,
162
191
  action,
163
192
  ctrl: ev.ctrlKey,
164
- alt: ev.altKey,
193
+ alt: stripAltFromReport ? false : ev.altKey,
165
194
  shift: ev.shiftKey
166
195
  });
167
196
  }
@@ -353,6 +382,21 @@ export class MouseService implements IMouseService {
353
382
  this._touchScrollAccumulator = 0;
354
383
  }
355
384
 
385
+ private _syncMouseModeState(element: HTMLElement): void {
386
+ if (this._mouseStateService.areMouseEventsActive) {
387
+ if (this._optionsService.rawOptions.mouseEventsRequireAlt) {
388
+ this._altMouseCursor?.resetClass();
389
+ this._selectionService.enable();
390
+ } else {
391
+ element.classList.add(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
392
+ this._selectionService.disable();
393
+ }
394
+ } else {
395
+ element.classList.remove(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
396
+ this._selectionService.enable();
397
+ }
398
+ }
399
+
356
400
  private _handleProtocolChange(ctx: IMouseBindContext, eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener>, events: CoreMouseEventType): void {
357
401
  const { element, document } = ctx.target;
358
402
  const { requestedEvents } = ctx;
@@ -361,13 +405,11 @@ export class MouseService implements IMouseService {
361
405
  if (this._optionsService.rawOptions.logLevel === 'debug') {
362
406
  this._logService.debug('Binding to mouse events:', this._explainEvents(events));
363
407
  }
364
- element.classList.add('enable-mouse-events');
365
- this._selectionService.disable();
366
408
  } else {
367
409
  this._logService.debug('Unbinding from mouse events.');
368
- element.classList.remove('enable-mouse-events');
369
- this._selectionService.enable();
370
410
  }
411
+ this._syncMouseModeState(element);
412
+ this._altMouseCursor?.sync();
371
413
 
372
414
  // add/remove handlers from requestedEvents
373
415
  if (!(events & CoreMouseEventType.MOVE)) {
@@ -537,3 +579,64 @@ export class MouseService implements IMouseService {
537
579
  }
538
580
 
539
581
  }
582
+
583
+ /**
584
+ * Toggles MouseEventCssClasses.ENABLE_MOUSE_EVENTS on the terminal element while alt is held when
585
+ * `mouseEventsRequireAlt` is active. DOM listeners are only registered while active.
586
+ */
587
+ export class AltMouseCursorController implements IDisposable {
588
+ private readonly _listeners = new MutableDisposable<IDisposable>();
589
+
590
+ constructor(
591
+ private readonly _element: HTMLElement,
592
+ private readonly _document: Document,
593
+ private readonly _isActive: () => boolean
594
+ ) {
595
+ }
596
+
597
+ public dispose(): void {
598
+ this._listeners.dispose();
599
+ }
600
+
601
+ public sync(): void {
602
+ this._listeners.clear();
603
+
604
+ if (!this._isActive()) {
605
+ return;
606
+ }
607
+
608
+ const store = new DisposableStore();
609
+ const syncFromModifier = (ev: KeyboardEvent | MouseEvent): void => this.syncFromModifier(ev);
610
+ store.add(addDisposableListener(this._document, 'keydown', syncFromModifier));
611
+ store.add(addDisposableListener(this._document, 'keyup', syncFromModifier));
612
+ store.add(addDisposableListener(this._element, 'mousemove', syncFromModifier));
613
+ const targetWindow = this._element.ownerDocument?.defaultView;
614
+ if (targetWindow) {
615
+ store.add(addDisposableListener(targetWindow, 'blur', () => {
616
+ if (this._isActive()) {
617
+ this.resetClass();
618
+ }
619
+ }));
620
+ }
621
+ this._listeners.value = store;
622
+ }
623
+
624
+ public resetClass(): void {
625
+ this._updateClass(false);
626
+ }
627
+
628
+ public syncFromModifier(ev: KeyboardEvent | MouseEvent): void {
629
+ if (!this._isActive()) {
630
+ return;
631
+ }
632
+ this._updateClass(ev.getModifierState('Alt'));
633
+ }
634
+
635
+ private _updateClass(altHeld: boolean): void {
636
+ if (altHeld) {
637
+ this._element.classList.add(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
638
+ } else {
639
+ this._element.classList.remove(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
640
+ }
641
+ }
642
+ }
@@ -15,7 +15,7 @@ import { IBufferLine, ICellData, IDisposable } from 'common/Types';
15
15
  import { getRangeLength } from 'common/buffer/BufferRange';
16
16
  import { CellData } from 'common/buffer/CellData';
17
17
  import { IBuffer } from 'common/buffer/Types';
18
- import { IBufferService, ICoreService, IOptionsService } from 'common/services/Services';
18
+ import { IBufferService, ICoreService, IMouseStateService, IOptionsService } from 'common/services/Services';
19
19
  import { Emitter } from 'common/Event';
20
20
 
21
21
  const enum Constants {
@@ -127,6 +127,7 @@ export class SelectionService extends Disposable implements ISelectionService {
127
127
  @ICoreService private readonly _coreService: ICoreService,
128
128
  @IMouseCoordsService private readonly _mouseCoordsService: IMouseCoordsService,
129
129
  @IOptionsService private readonly _optionsService: IOptionsService,
130
+ @IMouseStateService private readonly _mouseStateService: IMouseStateService,
130
131
  @IRenderService private readonly _renderService: IRenderService,
131
132
  @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
132
133
  ) {
@@ -434,6 +435,10 @@ export class SelectionService extends Disposable implements ISelectionService {
434
435
  * @param event The mouse event.
435
436
  */
436
437
  public shouldForceSelection(event: MouseEvent): boolean {
438
+ if (this._optionsService.rawOptions.mouseEventsRequireAlt && this._mouseStateService.areMouseEventsActive) {
439
+ return !event.altKey;
440
+ }
441
+
437
442
  if (Browser.isMac) {
438
443
  return event.altKey && this._optionsService.rawOptions.macOptionClickForcesSelection;
439
444
  }
@@ -458,6 +463,10 @@ export class SelectionService extends Disposable implements ISelectionService {
458
463
  return;
459
464
  }
460
465
 
466
+ if (this._optionsService.rawOptions.mouseEventsRequireAlt && this._mouseStateService.areMouseEventsActive && event.altKey) {
467
+ return;
468
+ }
469
+
461
470
  // Allow selection when using a specific modifier key, even when disabled
462
471
  if (!this._enabled) {
463
472
  if (!this.shouldForceSelection(event)) {
@@ -596,6 +605,9 @@ export class SelectionService extends Disposable implements ISelectionService {
596
605
  * @param event the mouse or keyboard event
597
606
  */
598
607
  public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean {
608
+ if (this._optionsService.rawOptions.mouseEventsRequireAlt && this._mouseStateService.areMouseEventsActive) {
609
+ return false;
610
+ }
599
611
  return event.altKey && !(Browser.isMac && this._optionsService.rawOptions.macOptionClickForcesSelection);
600
612
  }
601
613
 
@@ -6,4 +6,4 @@
6
6
  /**
7
7
  * The xterm.js version. This is updated by the publish script from package.json.
8
8
  */
9
- export const XTERM_VERSION = '6.1.0-beta.254';
9
+ export const XTERM_VERSION = '6.1.0-beta.256';
@@ -41,6 +41,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
41
41
  macOptionIsMeta: false,
42
42
  macOptionClickForcesSelection: false,
43
43
  minimumContrastRatio: 1,
44
+ mouseEventsRequireAlt: false,
44
45
  disableStdin: false,
45
46
  allowProposedApi: false,
46
47
  allowTransparency: false,
@@ -234,6 +234,7 @@ export interface ITerminalOptions {
234
234
  macOptionIsMeta?: boolean;
235
235
  macOptionClickForcesSelection?: boolean;
236
236
  minimumContrastRatio?: number;
237
+ mouseEventsRequireAlt?: boolean;
237
238
  reflowCursorLine?: boolean;
238
239
  rescaleOverlappingGlyphs?: boolean;
239
240
  rightClickSelectsWord?: boolean;
@@ -206,6 +206,17 @@ declare module '@xterm/xterm' {
206
206
  */
207
207
  minimumContrastRatio?: number;
208
208
 
209
+ /**
210
+ * When enabled and the terminal is in mouse events mode, mouse click, drag,
211
+ * and move events are only sent to the underlying application when the alt
212
+ * key is held. The alt key is not included in the mouse reports sent to the
213
+ * application. Wheel events are not affected. This allows normal text
214
+ * selection by default while still supporting application mouse interaction
215
+ * and scrolling when holding alt. When enabled, this takes precedence over
216
+ * `macOptionClickForcesSelection`.
217
+ */
218
+ mouseEventsRequireAlt?: boolean;
219
+
209
220
  /**
210
221
  * Control various quirks features that are either non-standard or standard
211
222
  * in but generally rejected in modern terminals.