@xterm/xterm 6.1.0-beta.19 → 6.1.0-beta.190

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.
Files changed (157) hide show
  1. package/README.md +60 -38
  2. package/css/xterm.css +29 -22
  3. package/lib/xterm.js +1 -1
  4. package/lib/xterm.js.map +1 -1
  5. package/lib/xterm.mjs +8 -34
  6. package/lib/xterm.mjs.map +4 -4
  7. package/package.json +25 -14
  8. package/src/browser/AccessibilityManager.ts +6 -3
  9. package/src/browser/Clipboard.ts +6 -3
  10. package/src/browser/CoreBrowserTerminal.ts +147 -318
  11. package/src/browser/Dom.ts +178 -0
  12. package/src/browser/Linkifier.ts +11 -11
  13. package/src/browser/OscLinkProvider.ts +3 -1
  14. package/src/browser/RenderDebouncer.ts +2 -2
  15. package/src/browser/TimeBasedDebouncer.ts +2 -2
  16. package/src/browser/Types.ts +12 -11
  17. package/src/browser/Viewport.ts +37 -20
  18. package/src/browser/decorations/BufferDecorationRenderer.ts +1 -1
  19. package/src/browser/decorations/OverviewRulerRenderer.ts +15 -16
  20. package/src/browser/input/CompositionHelper.ts +44 -8
  21. package/src/browser/public/Terminal.ts +25 -28
  22. package/src/browser/renderer/dom/DomRenderer.ts +128 -8
  23. package/src/browser/renderer/dom/DomRendererRowFactory.ts +19 -13
  24. package/src/browser/renderer/dom/WidthCache.ts +54 -52
  25. package/src/browser/renderer/shared/Constants.ts +7 -0
  26. package/src/browser/renderer/shared/TextBlinkStateManager.ts +97 -0
  27. package/src/browser/renderer/shared/Types.ts +8 -2
  28. package/src/browser/scrollable/abstractScrollbar.ts +300 -0
  29. package/src/browser/scrollable/fastDomNode.ts +126 -0
  30. package/src/browser/scrollable/globalPointerMoveMonitor.ts +90 -0
  31. package/src/browser/scrollable/horizontalScrollbar.ts +85 -0
  32. package/src/browser/scrollable/mouseEvent.ts +292 -0
  33. package/src/browser/scrollable/scrollable.ts +486 -0
  34. package/src/browser/scrollable/scrollableElement.ts +579 -0
  35. package/src/browser/scrollable/scrollableElementOptions.ts +161 -0
  36. package/src/browser/scrollable/scrollbarArrow.ts +110 -0
  37. package/src/browser/scrollable/scrollbarState.ts +246 -0
  38. package/src/browser/scrollable/scrollbarVisibilityController.ts +113 -0
  39. package/src/browser/scrollable/touch.ts +485 -0
  40. package/src/browser/scrollable/verticalScrollbar.ts +143 -0
  41. package/src/browser/scrollable/widget.ts +23 -0
  42. package/src/browser/services/CharSizeService.ts +2 -2
  43. package/src/browser/services/CoreBrowserService.ts +7 -5
  44. package/src/browser/services/KeyboardService.ts +67 -0
  45. package/src/browser/services/LinkProviderService.ts +1 -1
  46. package/src/browser/services/MouseCoordsService.ts +47 -0
  47. package/src/browser/services/MouseService.ts +518 -25
  48. package/src/browser/services/RenderService.ts +22 -15
  49. package/src/browser/services/SelectionService.ts +16 -8
  50. package/src/browser/services/Services.ts +40 -17
  51. package/src/browser/services/ThemeService.ts +2 -2
  52. package/src/common/Async.ts +105 -0
  53. package/src/common/CircularList.ts +2 -2
  54. package/src/common/Color.ts +8 -0
  55. package/src/common/CoreTerminal.ts +28 -18
  56. package/src/common/Event.ts +118 -0
  57. package/src/common/InputHandler.ts +256 -36
  58. package/src/common/Lifecycle.ts +113 -0
  59. package/src/common/Platform.ts +13 -3
  60. package/src/common/SortedList.ts +7 -3
  61. package/src/common/TaskQueue.ts +9 -3
  62. package/src/common/Types.ts +35 -15
  63. package/src/common/Version.ts +9 -0
  64. package/src/common/buffer/Buffer.ts +20 -14
  65. package/src/common/buffer/BufferLine.ts +4 -5
  66. package/src/common/buffer/BufferSet.ts +7 -6
  67. package/src/common/buffer/CellData.ts +57 -0
  68. package/src/common/buffer/Marker.ts +2 -2
  69. package/src/common/buffer/Types.ts +6 -2
  70. package/src/common/data/EscapeSequences.ts +71 -70
  71. package/src/common/input/Keyboard.ts +14 -7
  72. package/src/common/input/KittyKeyboard.ts +496 -0
  73. package/src/common/input/Win32InputMode.ts +297 -0
  74. package/src/common/input/WriteBuffer.ts +34 -2
  75. package/src/common/input/XParseColor.ts +2 -2
  76. package/src/common/parser/ApcParser.ts +245 -0
  77. package/src/common/parser/Constants.ts +22 -4
  78. package/src/common/parser/DcsParser.ts +5 -5
  79. package/src/common/parser/EscapeSequenceParser.ts +75 -22
  80. package/src/common/parser/OscParser.ts +5 -5
  81. package/src/common/parser/Types.ts +34 -1
  82. package/src/common/public/BufferLineApiView.ts +2 -2
  83. package/src/common/public/BufferNamespaceApi.ts +2 -2
  84. package/src/common/public/ParserApi.ts +3 -0
  85. package/src/common/services/BufferService.ts +8 -5
  86. package/src/common/services/CharsetService.ts +4 -0
  87. package/src/common/services/CoreService.ts +18 -4
  88. package/src/common/services/DecorationService.ts +24 -8
  89. package/src/common/services/LogService.ts +1 -31
  90. package/src/common/services/{CoreMouseService.ts → MouseStateService.ts} +21 -132
  91. package/src/common/services/OptionsService.ts +13 -4
  92. package/src/common/services/Services.ts +47 -40
  93. package/src/common/services/UnicodeService.ts +1 -1
  94. package/typings/xterm.d.ts +319 -35
  95. package/src/common/TypedArrayUtils.ts +0 -17
  96. package/src/vs/base/browser/browser.ts +0 -141
  97. package/src/vs/base/browser/canIUse.ts +0 -49
  98. package/src/vs/base/browser/dom.ts +0 -2369
  99. package/src/vs/base/browser/fastDomNode.ts +0 -316
  100. package/src/vs/base/browser/globalPointerMoveMonitor.ts +0 -112
  101. package/src/vs/base/browser/iframe.ts +0 -135
  102. package/src/vs/base/browser/keyboardEvent.ts +0 -213
  103. package/src/vs/base/browser/mouseEvent.ts +0 -229
  104. package/src/vs/base/browser/touch.ts +0 -372
  105. package/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +0 -303
  106. package/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +0 -114
  107. package/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +0 -720
  108. package/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +0 -165
  109. package/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +0 -114
  110. package/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +0 -243
  111. package/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +0 -118
  112. package/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +0 -116
  113. package/src/vs/base/browser/ui/widget.ts +0 -57
  114. package/src/vs/base/browser/window.ts +0 -14
  115. package/src/vs/base/common/arrays.ts +0 -887
  116. package/src/vs/base/common/arraysFind.ts +0 -202
  117. package/src/vs/base/common/assert.ts +0 -71
  118. package/src/vs/base/common/async.ts +0 -1992
  119. package/src/vs/base/common/cancellation.ts +0 -148
  120. package/src/vs/base/common/charCode.ts +0 -450
  121. package/src/vs/base/common/collections.ts +0 -140
  122. package/src/vs/base/common/decorators.ts +0 -130
  123. package/src/vs/base/common/equals.ts +0 -146
  124. package/src/vs/base/common/errors.ts +0 -303
  125. package/src/vs/base/common/event.ts +0 -1778
  126. package/src/vs/base/common/functional.ts +0 -32
  127. package/src/vs/base/common/hash.ts +0 -316
  128. package/src/vs/base/common/iterator.ts +0 -159
  129. package/src/vs/base/common/keyCodes.ts +0 -526
  130. package/src/vs/base/common/keybindings.ts +0 -284
  131. package/src/vs/base/common/lazy.ts +0 -47
  132. package/src/vs/base/common/lifecycle.ts +0 -801
  133. package/src/vs/base/common/linkedList.ts +0 -142
  134. package/src/vs/base/common/map.ts +0 -202
  135. package/src/vs/base/common/numbers.ts +0 -98
  136. package/src/vs/base/common/observable.ts +0 -76
  137. package/src/vs/base/common/observableInternal/api.ts +0 -31
  138. package/src/vs/base/common/observableInternal/autorun.ts +0 -281
  139. package/src/vs/base/common/observableInternal/base.ts +0 -489
  140. package/src/vs/base/common/observableInternal/debugName.ts +0 -145
  141. package/src/vs/base/common/observableInternal/derived.ts +0 -428
  142. package/src/vs/base/common/observableInternal/lazyObservableValue.ts +0 -146
  143. package/src/vs/base/common/observableInternal/logging.ts +0 -328
  144. package/src/vs/base/common/observableInternal/promise.ts +0 -209
  145. package/src/vs/base/common/observableInternal/utils.ts +0 -610
  146. package/src/vs/base/common/platform.ts +0 -281
  147. package/src/vs/base/common/scrollable.ts +0 -522
  148. package/src/vs/base/common/sequence.ts +0 -34
  149. package/src/vs/base/common/stopwatch.ts +0 -43
  150. package/src/vs/base/common/strings.ts +0 -557
  151. package/src/vs/base/common/symbols.ts +0 -9
  152. package/src/vs/base/common/uint.ts +0 -59
  153. package/src/vs/patches/nls.ts +0 -90
  154. package/src/vs/typings/base-common.d.ts +0 -20
  155. package/src/vs/typings/require.d.ts +0 -42
  156. package/src/vs/typings/vscode-globals-nls.d.ts +0 -36
  157. package/src/vs/typings/vscode-globals-product.d.ts +0 -33
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Copyright (c) 2025 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ import { IKeyboardService } from 'browser/services/Services';
7
+ import { evaluateKeyboardEvent } from 'common/input/Keyboard';
8
+ import { KittyKeyboard, KittyKeyboardEventType, KittyKeyboardFlags } from 'common/input/KittyKeyboard';
9
+ import { Win32InputMode } from 'common/input/Win32InputMode';
10
+ import { isMac } from 'common/Platform';
11
+ import { ICoreService, IOptionsService } from 'common/services/Services';
12
+ import { IKeyboardResult } from 'common/Types';
13
+
14
+ export class KeyboardService implements IKeyboardService {
15
+ public serviceBrand: undefined;
16
+
17
+ private _win32InputMode: Win32InputMode | undefined;
18
+ private _kittyKeyboard: KittyKeyboard | undefined;
19
+
20
+ constructor(
21
+ @ICoreService private readonly _coreService: ICoreService,
22
+ @IOptionsService private readonly _optionsService: IOptionsService
23
+ ) {
24
+ }
25
+
26
+ private _getWin32InputMode(): Win32InputMode {
27
+ this._win32InputMode ??= new Win32InputMode();
28
+ return this._win32InputMode;
29
+ }
30
+
31
+ private _getKittyKeyboard(): KittyKeyboard {
32
+ this._kittyKeyboard ??= new KittyKeyboard();
33
+ return this._kittyKeyboard;
34
+ }
35
+
36
+ public evaluateKeyDown(event: KeyboardEvent): IKeyboardResult {
37
+ // Win32 input mode takes priority (most raw)
38
+ if (this.useWin32InputMode) {
39
+ return this._getWin32InputMode().evaluateKeyboardEvent(event, true);
40
+ }
41
+ const kittyFlags = this._coreService.kittyKeyboard.flags;
42
+ return this.useKitty
43
+ ? this._getKittyKeyboard().evaluate(event, kittyFlags, event.repeat ? KittyKeyboardEventType.REPEAT : KittyKeyboardEventType.PRESS)
44
+ : evaluateKeyboardEvent(event, this._coreService.decPrivateModes.applicationCursorKeys, isMac, this._optionsService.rawOptions.macOptionIsMeta);
45
+ }
46
+
47
+ public evaluateKeyUp(event: KeyboardEvent): IKeyboardResult | undefined {
48
+ // Win32 input mode sends key up events
49
+ if (this.useWin32InputMode) {
50
+ return this._getWin32InputMode().evaluateKeyboardEvent(event, false);
51
+ }
52
+ const kittyFlags = this._coreService.kittyKeyboard.flags;
53
+ if (this.useKitty && (kittyFlags & KittyKeyboardFlags.REPORT_EVENT_TYPES)) {
54
+ return this._getKittyKeyboard().evaluate(event, kittyFlags, KittyKeyboardEventType.RELEASE);
55
+ }
56
+ return undefined;
57
+ }
58
+
59
+ public get useKitty(): boolean {
60
+ const kittyFlags = this._coreService.kittyKeyboard.flags;
61
+ return !!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard && KittyKeyboard.shouldUseProtocol(kittyFlags));
62
+ }
63
+
64
+ public get useWin32InputMode(): boolean {
65
+ return !!(this._optionsService.rawOptions.vtExtensions?.win32InputMode && this._coreService.decPrivateModes.win32InputMode);
66
+ }
67
+ }
@@ -1,5 +1,5 @@
1
1
  import { ILinkProvider, ILinkProviderService } from 'browser/services/Services';
2
- import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
2
+ import { Disposable, toDisposable } from 'common/Lifecycle';
3
3
  import { IDisposable } from 'common/Types';
4
4
 
5
5
  export class LinkProviderService extends Disposable implements ILinkProviderService {
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Copyright (c) 2026 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ import { getWindow } from 'browser/Dom';
7
+ import { getCoords, getCoordsRelativeToElement } from 'browser/input/Mouse';
8
+ import { ICharSizeService, IMouseCoordsService, IRenderService } from 'browser/services/Services';
9
+
10
+ export class MouseCoordsService implements IMouseCoordsService {
11
+ public serviceBrand: undefined;
12
+
13
+ constructor(
14
+ @ICharSizeService private readonly _charSizeService: ICharSizeService,
15
+ @IRenderService private readonly _renderService: IRenderService
16
+ ) {
17
+ }
18
+
19
+ public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
20
+ return getCoords(
21
+ window,
22
+ event,
23
+ element,
24
+ colCount,
25
+ rowCount,
26
+ this._charSizeService.hasValidSize,
27
+ this._renderService.dimensions.css.cell.width,
28
+ this._renderService.dimensions.css.cell.height,
29
+ isSelection
30
+ );
31
+ }
32
+
33
+ public getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined {
34
+ const coords = getCoordsRelativeToElement(getWindow(element), event, element);
35
+ if (!this._charSizeService.hasValidSize) {
36
+ return undefined;
37
+ }
38
+ coords[0] = Math.min(Math.max(coords[0], 0), this._renderService.dimensions.css.canvas.width - 1);
39
+ coords[1] = Math.min(Math.max(coords[1], 0), this._renderService.dimensions.css.canvas.height - 1);
40
+ return {
41
+ col: Math.floor(coords[0] / this._renderService.dimensions.css.cell.width),
42
+ row: Math.floor(coords[1] / this._renderService.dimensions.css.cell.height),
43
+ x: Math.floor(coords[0]),
44
+ y: Math.floor(coords[1])
45
+ };
46
+ }
47
+ }
@@ -3,44 +3,537 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
- import { ICharSizeService, IRenderService, IMouseService } from './Services';
7
- import { getCoords, getCoordsRelativeToElement } from 'browser/input/Mouse';
6
+ import { addDisposableListener } from 'browser/Dom';
7
+ import { IBufferService, IMouseStateService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
8
+ import { CoreMouseAction, CoreMouseButton, CoreMouseEventType, ICoreMouseEvent, IDisposable } from 'common/Types';
9
+ import { C0 } from 'common/data/EscapeSequences';
10
+ import { toDisposable } from 'common/Lifecycle';
11
+ import { ICoreBrowserService, IMouseCoordsService, IMouseService, IMouseServiceTarget, IRenderService, ISelectionService } from './Services';
12
+ import { Gesture, EventType as GestureEventType, IGestureEvent } from 'browser/scrollable/touch';
13
+
14
+ type RequestedMouseEvents = Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener | null>;
15
+
16
+ interface IMouseBindContext {
17
+ readonly target: IMouseServiceTarget;
18
+ readonly focus: () => void;
19
+ readonly requestedEvents: RequestedMouseEvents;
20
+ }
8
21
 
9
22
  export class MouseService implements IMouseService {
10
23
  public serviceBrand: undefined;
11
24
 
25
+ private _lastEvent: ICoreMouseEvent | null = null;
26
+ private _wheelPartialScroll: number = 0;
27
+ private _touchScrollAccumulator: number = 0;
28
+
12
29
  constructor(
13
30
  @IRenderService private readonly _renderService: IRenderService,
14
- @ICharSizeService private readonly _charSizeService: ICharSizeService
31
+ @IMouseCoordsService private readonly _mouseCoordsService: IMouseCoordsService,
32
+ @IMouseStateService private readonly _mouseStateService: IMouseStateService,
33
+ @ICoreService private readonly _coreService: ICoreService,
34
+ @IBufferService private readonly _bufferService: IBufferService,
35
+ @IOptionsService private readonly _optionsService: IOptionsService,
36
+ @ISelectionService private readonly _selectionService: ISelectionService,
37
+ @ILogService private readonly _logService: ILogService,
38
+ @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
15
39
  ) {
16
40
  }
17
41
 
18
- public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
19
- return getCoords(
20
- window,
21
- event,
22
- element,
23
- colCount,
24
- rowCount,
25
- this._charSizeService.hasValidSize,
26
- this._renderService.dimensions.css.cell.width,
27
- this._renderService.dimensions.css.cell.height,
28
- isSelection
29
- );
42
+ public bindMouse(target: IMouseServiceTarget, register: (disposable: IDisposable) => void, focus: () => void): void {
43
+ const { element, document } = target;
44
+
45
+ /**
46
+ * Event listener state handling.
47
+ * We listen to the onProtocolChange event of MouseStateService and put
48
+ * requested listeners in `requestedEvents`. With this the listeners
49
+ * have all bits to do the event listener juggling.
50
+ * Note: 'mousedown' currently is "always on" and not managed
51
+ * by onProtocolChange.
52
+ */
53
+ const requestedEvents: RequestedMouseEvents = {
54
+ mouseup: null,
55
+ wheel: null,
56
+ mousedrag: null,
57
+ mousemove: null
58
+ };
59
+ const ctx: IMouseBindContext = { target, focus, requestedEvents };
60
+ const eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener> = {
61
+ mouseup: (ev: Event) => this._handleMouseUp(ctx, ev as MouseEvent),
62
+ wheel: (ev: Event) => this._handleWheel(ctx, ev as WheelEvent),
63
+ mousedrag: (ev: Event) => this._handleMouseDrag(ctx, ev as MouseEvent),
64
+ mousemove: (ev: Event) => this._handleMouseMove(ctx, ev as MouseEvent)
65
+ };
66
+ register(this._mouseStateService.onProtocolChange(events => {
67
+ this._handleProtocolChange(ctx, eventListeners, events);
68
+ }));
69
+ // force initial onProtocolChange so we dont miss early mouse requests
70
+ this._mouseStateService.activeProtocol = this._mouseStateService.activeProtocol;
71
+
72
+ // Ensure document-level listeners are removed on dispose
73
+ register(toDisposable(() => {
74
+ if (requestedEvents.mouseup) {
75
+ document.removeEventListener('mouseup', requestedEvents.mouseup);
76
+ }
77
+ if (requestedEvents.mousedrag) {
78
+ document.removeEventListener('mousemove', requestedEvents.mousedrag);
79
+ }
80
+ }));
81
+
82
+ /**
83
+ * "Always on" event listeners.
84
+ */
85
+ register(addDisposableListener(element, 'mousedown', (ev: MouseEvent) => this._handleMouseDown(ctx, ev)));
86
+ register(addDisposableListener(element, 'wheel', (ev: WheelEvent) => this._handlePassiveWheel(ctx, ev), { passive: false }));
87
+ register(Gesture.addTarget(target.screenElement));
88
+ register(addDisposableListener(target.screenElement, GestureEventType.START, () => this._handleTouchStart()));
89
+ register(addDisposableListener(target.screenElement, GestureEventType.CHANGE, (e: IGestureEvent) => this._handleTouchChange(ctx, e)));
90
+ }
91
+
92
+ private _sendEvent(ctx: IMouseBindContext, ev: MouseEvent | WheelEvent): boolean {
93
+ // Get mouse coordinates
94
+ const pos = this._mouseCoordsService.getMouseReportCoords(ev as MouseEvent, ctx.target.screenElement);
95
+ if (!pos) {
96
+ return false;
97
+ }
98
+
99
+ let but: CoreMouseButton;
100
+ let action: CoreMouseAction | undefined;
101
+ switch ((ev as MouseEvent & { overrideType?: string }).overrideType || ev.type) {
102
+ case 'mousemove':
103
+ action = CoreMouseAction.MOVE;
104
+ if (ev.buttons === undefined) {
105
+ // buttons is not supported on macOS, try to get a value from button instead
106
+ but = CoreMouseButton.NONE;
107
+ if (ev.button !== undefined) {
108
+ but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
109
+ }
110
+ } else {
111
+ // according to MDN buttons only reports up to button 5 (AUX2)
112
+ but = ev.buttons & 1 ? CoreMouseButton.LEFT :
113
+ ev.buttons & 4 ? CoreMouseButton.MIDDLE :
114
+ ev.buttons & 2 ? CoreMouseButton.RIGHT :
115
+ CoreMouseButton.NONE; // fallback to NONE
116
+ }
117
+ break;
118
+ case 'mouseup':
119
+ action = CoreMouseAction.UP;
120
+ but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
121
+ break;
122
+ case 'mousedown':
123
+ action = CoreMouseAction.DOWN;
124
+ but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
125
+ break;
126
+ case 'wheel':
127
+ if (!this._mouseStateService.allowCustomWheelEvent(ev as WheelEvent)) {
128
+ return false;
129
+ }
130
+ const deltaY = (ev as WheelEvent).deltaY;
131
+ if (deltaY === 0) {
132
+ return false;
133
+ }
134
+ const lines = this._consumeWheelEvent(
135
+ ev as WheelEvent,
136
+ this._renderService?.dimensions?.device?.cell?.height,
137
+ this._coreBrowserService?.dpr
138
+ );
139
+ if (lines === 0) {
140
+ return false;
141
+ }
142
+ action = deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;
143
+ but = CoreMouseButton.WHEEL;
144
+ break;
145
+ default:
146
+ // dont handle other event types by accident
147
+ return false;
148
+ }
149
+
150
+ // exit if we cannot determine valid button/action values
151
+ // do nothing for higher buttons than wheel
152
+ if (action === undefined || but === undefined || but > CoreMouseButton.WHEEL) {
153
+ return false;
154
+ }
155
+
156
+ return this._triggerMouseEvent({
157
+ col: pos.col,
158
+ row: pos.row,
159
+ x: pos.x,
160
+ y: pos.y,
161
+ button: but,
162
+ action,
163
+ ctrl: ev.ctrlKey,
164
+ alt: ev.altKey,
165
+ shift: ev.shiftKey
166
+ });
167
+ }
168
+
169
+ private _handleMouseUp(ctx: IMouseBindContext, ev: MouseEvent): void {
170
+ this._sendEvent(ctx, ev);
171
+ if (!ev.buttons) {
172
+ // if no other button is held remove global handlers
173
+ if (ctx.requestedEvents.mouseup) {
174
+ ctx.target.document.removeEventListener('mouseup', ctx.requestedEvents.mouseup);
175
+ }
176
+ if (ctx.requestedEvents.mousedrag) {
177
+ ctx.target.document.removeEventListener('mousemove', ctx.requestedEvents.mousedrag);
178
+ }
179
+ }
180
+ }
181
+
182
+ private _handleWheel(ctx: IMouseBindContext, ev: WheelEvent): false {
183
+ this._sendEvent(ctx, ev);
184
+ ev.preventDefault();
185
+ ev.stopPropagation();
186
+ return false;
187
+ }
188
+
189
+ private _handleMouseDrag(ctx: IMouseBindContext, ev: MouseEvent): void {
190
+ // deal only with move while a button is held
191
+ if (ev.buttons) {
192
+ this._sendEvent(ctx, ev);
193
+ }
194
+ }
195
+
196
+ private _handleMouseMove(ctx: IMouseBindContext, ev: MouseEvent): void {
197
+ // deal only with move without any button
198
+ if (!ev.buttons) {
199
+ this._sendEvent(ctx, ev);
200
+ }
201
+ }
202
+
203
+ private _handleMouseDown(ctx: IMouseBindContext, ev: MouseEvent): void {
204
+ ev.preventDefault();
205
+ ctx.focus();
206
+
207
+ // Don't send the mouse button to the pty if mouse events are disabled or
208
+ // if the selection manager is having selection forced (ie. a modifier is
209
+ // held).
210
+ if (!this._mouseStateService.areMouseEventsActive || this._selectionService.shouldForceSelection(ev)) {
211
+ return;
212
+ }
213
+
214
+ this._sendEvent(ctx, ev);
215
+
216
+ // Register additional global handlers which should keep reporting outside
217
+ // of the terminal element.
218
+ // Note: Other emulators also do this for 'mousedown' while a button
219
+ // is held, we currently limit 'mousedown' to the terminal only.
220
+ if (ctx.requestedEvents.mouseup) {
221
+ ctx.target.document.addEventListener('mouseup', ctx.requestedEvents.mouseup);
222
+ }
223
+ if (ctx.requestedEvents.mousedrag) {
224
+ ctx.target.document.addEventListener('mousemove', ctx.requestedEvents.mousedrag);
225
+ }
30
226
  }
31
227
 
32
- public getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined {
33
- const coords = getCoordsRelativeToElement(window, event, element);
34
- if (!this._charSizeService.hasValidSize) {
35
- return undefined;
228
+ private _handlePassiveWheel(ctx: IMouseBindContext, ev: WheelEvent): false | void {
229
+ // do nothing, if app side handles wheel itself
230
+ if (ctx.requestedEvents.wheel) {
231
+ return;
36
232
  }
37
- coords[0] = Math.min(Math.max(coords[0], 0), this._renderService.dimensions.css.canvas.width - 1);
38
- coords[1] = Math.min(Math.max(coords[1], 0), this._renderService.dimensions.css.canvas.height - 1);
233
+
234
+ if (!this._mouseStateService.allowCustomWheelEvent(ev)) {
235
+ return false;
236
+ }
237
+
238
+ if (!this._bufferService.buffer.hasScrollback) {
239
+ // Convert wheel events into up/down events when the buffer does not have scrollback, this
240
+ // enables scrolling in apps hosted in the alt buffer such as vim or tmux even when mouse
241
+ // events are not enabled.
242
+ // This used implementation used get the actual lines/partial lines scrolled from the
243
+ // viewport but since moving to the new viewport implementation has been simplified to
244
+ // simply send a single up or down sequence.
245
+
246
+ // Do nothing if there's no vertical scroll
247
+ const deltaY = ev.deltaY;
248
+ if (deltaY === 0) {
249
+ return false;
250
+ }
251
+
252
+ const lines = this._consumeWheelEvent(
253
+ ev,
254
+ this._renderService?.dimensions?.device?.cell?.height,
255
+ this._coreBrowserService?.dpr
256
+ );
257
+ if (lines === 0) {
258
+ ev.preventDefault();
259
+ ev.stopPropagation();
260
+ return false;
261
+ }
262
+
263
+ // Construct and send sequences
264
+ const sequence = C0.ESC + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
265
+ this._coreService.triggerDataEvent(sequence, true);
266
+ ev.preventDefault();
267
+ ev.stopPropagation();
268
+ return false;
269
+ }
270
+ }
271
+
272
+ private _handleTouchStart(): void {
273
+ this._touchScrollAccumulator = 0;
274
+ }
275
+
276
+ private _handleTouchChange(ctx: IMouseBindContext, e: IGestureEvent): void {
277
+ e.preventDefault();
278
+ e.stopPropagation();
279
+
280
+ // When mouse protocol has wheel events active, send as mouse wheel events.
281
+ if (ctx.requestedEvents.wheel) {
282
+ this._handleTouchScrollAsWheel(ctx, e);
283
+ return;
284
+ }
285
+
286
+ // When in alt buffer (no scrollback), send up/down key sequences.
287
+ if (!this._bufferService.buffer.hasScrollback) {
288
+ this._handleTouchScrollAsKeys(e);
289
+ return;
290
+ }
291
+
292
+ // Normal scrollback: delegate to viewport scrolling when available.
293
+ ctx.target.handleTouchScroll?.(e.translationY);
294
+ }
295
+
296
+ private _handleTouchScrollAsKeys(e: IGestureEvent): void {
297
+ const cellHeight = this._renderService?.dimensions.css.cell.height;
298
+ if (!cellHeight) {
299
+ return;
300
+ }
301
+
302
+ this._touchScrollAccumulator -= e.translationY;
303
+ const lines = Math.trunc(this._touchScrollAccumulator / cellHeight);
304
+ if (lines === 0) {
305
+ return;
306
+ }
307
+
308
+ this._touchScrollAccumulator -= lines * cellHeight;
309
+ const sequence = C0.ESC
310
+ + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[')
311
+ + (lines < 0 ? 'A' : 'B');
312
+ for (let i = 0; i < Math.abs(lines); i++) {
313
+ this._coreService.triggerDataEvent(sequence, true);
314
+ }
315
+ }
316
+
317
+ private _handleTouchScrollAsWheel(ctx: IMouseBindContext, e: IGestureEvent): void {
318
+ const cellHeight = this._renderService?.dimensions.css.cell.height;
319
+ if (!cellHeight) {
320
+ return;
321
+ }
322
+
323
+ this._touchScrollAccumulator -= e.translationY;
324
+ const lines = Math.trunc(this._touchScrollAccumulator / cellHeight);
325
+ if (lines === 0) {
326
+ return;
327
+ }
328
+
329
+ this._touchScrollAccumulator -= lines * cellHeight;
330
+ const pos = this._mouseCoordsService.getMouseReportCoords(e, ctx.target.screenElement);
331
+ if (!pos) {
332
+ return;
333
+ }
334
+
335
+ for (let i = 0; i < Math.abs(lines); i++) {
336
+ this._triggerMouseEvent({
337
+ col: pos.col,
338
+ row: pos.row,
339
+ x: pos.x,
340
+ y: pos.y,
341
+ button: CoreMouseButton.WHEEL,
342
+ action: lines < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN,
343
+ ctrl: false,
344
+ alt: false,
345
+ shift: false
346
+ });
347
+ }
348
+ }
349
+
350
+ public reset(): void {
351
+ this._lastEvent = null;
352
+ this._wheelPartialScroll = 0;
353
+ this._touchScrollAccumulator = 0;
354
+ }
355
+
356
+ private _handleProtocolChange(ctx: IMouseBindContext, eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener>, events: CoreMouseEventType): void {
357
+ const { element, document } = ctx.target;
358
+ const { requestedEvents } = ctx;
359
+ // apply global changes on events
360
+ if (events) {
361
+ if (this._optionsService.rawOptions.logLevel === 'debug') {
362
+ this._logService.debug('Binding to mouse events:', this._explainEvents(events));
363
+ }
364
+ element.classList.add('enable-mouse-events');
365
+ this._selectionService.disable();
366
+ } else {
367
+ this._logService.debug('Unbinding from mouse events.');
368
+ element.classList.remove('enable-mouse-events');
369
+ this._selectionService.enable();
370
+ }
371
+
372
+ // add/remove handlers from requestedEvents
373
+ if (!(events & CoreMouseEventType.MOVE)) {
374
+ if (requestedEvents.mousemove) {
375
+ element.removeEventListener('mousemove', requestedEvents.mousemove);
376
+ }
377
+ requestedEvents.mousemove = null;
378
+ } else if (!requestedEvents.mousemove) {
379
+ element.addEventListener('mousemove', eventListeners.mousemove);
380
+ requestedEvents.mousemove = eventListeners.mousemove;
381
+ }
382
+
383
+ if (!(events & CoreMouseEventType.WHEEL)) {
384
+ if (requestedEvents.wheel) {
385
+ element.removeEventListener('wheel', requestedEvents.wheel);
386
+ }
387
+ requestedEvents.wheel = null;
388
+ } else if (!requestedEvents.wheel) {
389
+ element.addEventListener('wheel', eventListeners.wheel, { passive: false });
390
+ requestedEvents.wheel = eventListeners.wheel;
391
+ }
392
+
393
+ if (!(events & CoreMouseEventType.UP)) {
394
+ if (requestedEvents.mouseup) {
395
+ document.removeEventListener('mouseup', requestedEvents.mouseup);
396
+ }
397
+ requestedEvents.mouseup = null;
398
+ } else {
399
+ requestedEvents.mouseup ??= eventListeners.mouseup;
400
+ }
401
+
402
+ if (!(events & CoreMouseEventType.DRAG)) {
403
+ if (requestedEvents.mousedrag) {
404
+ document.removeEventListener('mousemove', requestedEvents.mousedrag);
405
+ }
406
+ requestedEvents.mousedrag = null;
407
+ } else {
408
+ requestedEvents.mousedrag ??= eventListeners.mousedrag;
409
+ }
410
+ }
411
+
412
+ private _applyScrollModifier(amount: number, ev: WheelEvent): number {
413
+ // Multiply the scroll speed when the modifier key is pressed
414
+ if (ev.altKey || ev.ctrlKey || ev.shiftKey) {
415
+ return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity;
416
+ }
417
+ return amount * this._optionsService.rawOptions.scrollSensitivity;
418
+ }
419
+
420
+ /**
421
+ * Processes a wheel event, accounting for partial scrolls for trackpad, mouse scrolls.
422
+ * This prevents hyper-sensitive scrolling in alt buffer.
423
+ */
424
+ private _consumeWheelEvent(ev: WheelEvent, cellHeight?: number, dpr?: number): number {
425
+ // Do nothing if it's not a vertical scroll event
426
+ if (ev.deltaY === 0 || ev.shiftKey) {
427
+ return 0;
428
+ }
429
+
430
+ if (cellHeight === undefined || dpr === undefined) {
431
+ return 0;
432
+ }
433
+
434
+ const targetWheelEventPixels = cellHeight / dpr;
435
+ let amount = this._applyScrollModifier(ev.deltaY, ev);
436
+
437
+ if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
438
+ amount /= (targetWheelEventPixels + 0.0); // Prevent integer division
439
+
440
+ const isLikelyTrackpad = Math.abs(ev.deltaY) < 50;
441
+ if (isLikelyTrackpad) {
442
+ amount *= 0.3;
443
+ }
444
+
445
+ this._wheelPartialScroll += amount;
446
+ amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);
447
+ this._wheelPartialScroll %= 1;
448
+ } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
449
+ amount *= this._bufferService.rows;
450
+ }
451
+ return amount;
452
+ }
453
+
454
+ /**
455
+ * Triggers a mouse event to be sent.
456
+ *
457
+ * Returns true if the event passed all protocol restrictions and a report
458
+ * was sent, otherwise false. The return value may be used to decide whether
459
+ * the default event action in the browser component should be omitted.
460
+ *
461
+ * Note: The method will change values of the given event object
462
+ * to fulfill protocol and encoding restrictions.
463
+ */
464
+ private _triggerMouseEvent(e: ICoreMouseEvent): boolean {
465
+ // range check for col/row
466
+ if (e.col < 0 || e.col >= this._bufferService.cols
467
+ || e.row < 0 || e.row >= this._bufferService.rows) {
468
+ return false;
469
+ }
470
+
471
+ // filter nonsense combinations of button + action
472
+ if (e.button === CoreMouseButton.WHEEL && e.action === CoreMouseAction.MOVE) {
473
+ return false;
474
+ }
475
+ if (e.button === CoreMouseButton.NONE && e.action !== CoreMouseAction.MOVE) {
476
+ return false;
477
+ }
478
+ if (e.button !== CoreMouseButton.WHEEL && (e.action === CoreMouseAction.LEFT || e.action === CoreMouseAction.RIGHT)) {
479
+ return false;
480
+ }
481
+
482
+ // report 1-based coords
483
+ e.col++;
484
+ e.row++;
485
+
486
+ // debounce move events at grid or pixel level
487
+ if (e.action === CoreMouseAction.MOVE
488
+ && this._lastEvent
489
+ && this._equalEvents(this._lastEvent, e, this._mouseStateService.isPixelEncoding)
490
+ ) {
491
+ return false;
492
+ }
493
+
494
+ // apply protocol restrictions
495
+ if (!this._mouseStateService.restrictMouseEvent(e)) {
496
+ return false;
497
+ }
498
+
499
+ // encode report and send
500
+ const report = this._mouseStateService.encodeMouseEvent(e);
501
+ if (report) {
502
+ if (this._mouseStateService.isDefaultEncoding) {
503
+ this._coreService.triggerBinaryEvent(report);
504
+ } else {
505
+ this._coreService.triggerDataEvent(report, true);
506
+ }
507
+ }
508
+
509
+ this._lastEvent = e;
510
+ return true;
511
+ }
512
+
513
+ private _explainEvents(events: CoreMouseEventType): { [event: string]: boolean } {
39
514
  return {
40
- col: Math.floor(coords[0] / this._renderService.dimensions.css.cell.width),
41
- row: Math.floor(coords[1] / this._renderService.dimensions.css.cell.height),
42
- x: Math.floor(coords[0]),
43
- y: Math.floor(coords[1])
515
+ down: !!(events & CoreMouseEventType.DOWN),
516
+ up: !!(events & CoreMouseEventType.UP),
517
+ drag: !!(events & CoreMouseEventType.DRAG),
518
+ move: !!(events & CoreMouseEventType.MOVE),
519
+ wheel: !!(events & CoreMouseEventType.WHEEL)
44
520
  };
45
521
  }
522
+
523
+ private _equalEvents(e1: ICoreMouseEvent, e2: ICoreMouseEvent, pixels: boolean): boolean {
524
+ if (pixels) {
525
+ if (e1.x !== e2.x) return false;
526
+ if (e1.y !== e2.y) return false;
527
+ } else {
528
+ if (e1.col !== e2.col) return false;
529
+ if (e1.row !== e2.row) return false;
530
+ }
531
+ if (e1.button !== e2.button) return false;
532
+ if (e1.action !== e2.action) return false;
533
+ if (e1.ctrl !== e2.ctrl) return false;
534
+ if (e1.alt !== e2.alt) return false;
535
+ if (e1.shift !== e2.shift) return false;
536
+ return true;
537
+ }
538
+
46
539
  }