@xterm/xterm 6.1.0-beta.2 → 6.1.0-beta.200

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 (158) 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 +36 -24
  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 +4 -2
  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 +55 -20
  18. package/src/browser/decorations/BufferDecorationRenderer.ts +1 -1
  19. package/src/browser/decorations/OverviewRulerRenderer.ts +33 -17
  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 +205 -41
  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 -16
  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 +29 -21
  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 +14 -5
  62. package/src/common/Types.ts +36 -16
  63. package/src/common/Version.ts +9 -0
  64. package/src/common/buffer/Buffer.ts +22 -16
  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/Charsets.ts +1 -1
  71. package/src/common/data/EscapeSequences.ts +71 -70
  72. package/src/common/input/Keyboard.ts +14 -7
  73. package/src/common/input/KittyKeyboard.ts +519 -0
  74. package/src/common/input/Win32InputMode.ts +297 -0
  75. package/src/common/input/WriteBuffer.ts +34 -2
  76. package/src/common/input/XParseColor.ts +2 -2
  77. package/src/common/parser/ApcParser.ts +245 -0
  78. package/src/common/parser/Constants.ts +22 -4
  79. package/src/common/parser/DcsParser.ts +5 -5
  80. package/src/common/parser/EscapeSequenceParser.ts +155 -43
  81. package/src/common/parser/OscParser.ts +5 -5
  82. package/src/common/parser/Types.ts +34 -1
  83. package/src/common/public/BufferLineApiView.ts +2 -2
  84. package/src/common/public/BufferNamespaceApi.ts +2 -2
  85. package/src/common/public/ParserApi.ts +3 -0
  86. package/src/common/services/BufferService.ts +8 -5
  87. package/src/common/services/CharsetService.ts +4 -0
  88. package/src/common/services/CoreService.ts +18 -4
  89. package/src/common/services/DecorationService.ts +24 -8
  90. package/src/common/services/LogService.ts +1 -31
  91. package/src/common/services/{CoreMouseService.ts → MouseStateService.ts} +21 -132
  92. package/src/common/services/OptionsService.ts +13 -7
  93. package/src/common/services/Services.ts +47 -44
  94. package/src/common/services/UnicodeService.ts +1 -1
  95. package/typings/xterm.d.ts +320 -45
  96. package/src/common/TypedArrayUtils.ts +0 -17
  97. package/src/vs/base/browser/browser.ts +0 -141
  98. package/src/vs/base/browser/canIUse.ts +0 -49
  99. package/src/vs/base/browser/dom.ts +0 -2369
  100. package/src/vs/base/browser/fastDomNode.ts +0 -316
  101. package/src/vs/base/browser/globalPointerMoveMonitor.ts +0 -112
  102. package/src/vs/base/browser/iframe.ts +0 -135
  103. package/src/vs/base/browser/keyboardEvent.ts +0 -213
  104. package/src/vs/base/browser/mouseEvent.ts +0 -229
  105. package/src/vs/base/browser/touch.ts +0 -372
  106. package/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +0 -303
  107. package/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +0 -114
  108. package/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +0 -720
  109. package/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +0 -165
  110. package/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +0 -114
  111. package/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +0 -243
  112. package/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +0 -118
  113. package/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +0 -116
  114. package/src/vs/base/browser/ui/widget.ts +0 -57
  115. package/src/vs/base/browser/window.ts +0 -14
  116. package/src/vs/base/common/arrays.ts +0 -887
  117. package/src/vs/base/common/arraysFind.ts +0 -202
  118. package/src/vs/base/common/assert.ts +0 -71
  119. package/src/vs/base/common/async.ts +0 -1992
  120. package/src/vs/base/common/cancellation.ts +0 -148
  121. package/src/vs/base/common/charCode.ts +0 -450
  122. package/src/vs/base/common/collections.ts +0 -140
  123. package/src/vs/base/common/decorators.ts +0 -130
  124. package/src/vs/base/common/equals.ts +0 -146
  125. package/src/vs/base/common/errors.ts +0 -303
  126. package/src/vs/base/common/event.ts +0 -1778
  127. package/src/vs/base/common/functional.ts +0 -32
  128. package/src/vs/base/common/hash.ts +0 -316
  129. package/src/vs/base/common/iterator.ts +0 -159
  130. package/src/vs/base/common/keyCodes.ts +0 -526
  131. package/src/vs/base/common/keybindings.ts +0 -284
  132. package/src/vs/base/common/lazy.ts +0 -47
  133. package/src/vs/base/common/lifecycle.ts +0 -801
  134. package/src/vs/base/common/linkedList.ts +0 -142
  135. package/src/vs/base/common/map.ts +0 -202
  136. package/src/vs/base/common/numbers.ts +0 -98
  137. package/src/vs/base/common/observable.ts +0 -76
  138. package/src/vs/base/common/observableInternal/api.ts +0 -31
  139. package/src/vs/base/common/observableInternal/autorun.ts +0 -281
  140. package/src/vs/base/common/observableInternal/base.ts +0 -489
  141. package/src/vs/base/common/observableInternal/debugName.ts +0 -145
  142. package/src/vs/base/common/observableInternal/derived.ts +0 -428
  143. package/src/vs/base/common/observableInternal/lazyObservableValue.ts +0 -146
  144. package/src/vs/base/common/observableInternal/logging.ts +0 -328
  145. package/src/vs/base/common/observableInternal/promise.ts +0 -209
  146. package/src/vs/base/common/observableInternal/utils.ts +0 -610
  147. package/src/vs/base/common/platform.ts +0 -281
  148. package/src/vs/base/common/scrollable.ts +0 -522
  149. package/src/vs/base/common/sequence.ts +0 -34
  150. package/src/vs/base/common/stopwatch.ts +0 -43
  151. package/src/vs/base/common/strings.ts +0 -557
  152. package/src/vs/base/common/symbols.ts +0 -9
  153. package/src/vs/base/common/uint.ts +0 -59
  154. package/src/vs/patches/nls.ts +0 -90
  155. package/src/vs/typings/base-common.d.ts +0 -20
  156. package/src/vs/typings/require.d.ts +0 -42
  157. package/src/vs/typings/vscode-globals-nls.d.ts +0 -36
  158. 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, isMac && this._optionsService.rawOptions.macOptionIsMeta)
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, isMac && this._optionsService.rawOptions.macOptionIsMeta);
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
  }