@xterm/xterm 6.1.0-beta.98 → 6.1.0-beta.99

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.98",
4
+ "version": "6.1.0-beta.99",
5
5
  "main": "lib/xterm.js",
6
6
  "module": "lib/xterm.mjs",
7
7
  "style": "css/xterm.css",
@@ -116,5 +116,5 @@
116
116
  "ws": "^8.2.3",
117
117
  "xterm-benchmark": "^0.3.1"
118
118
  },
119
- "commit": "934b5dc8faa8897398075e782f5a246544928feb"
119
+ "commit": "91c4761b8229e76f420e728b5f6ace1976bd42ec"
120
120
  }
@@ -39,8 +39,9 @@ import { LinkProviderService } from 'browser/services/LinkProviderService';
39
39
  import { MouseService } from 'browser/services/MouseService';
40
40
  import { RenderService } from 'browser/services/RenderService';
41
41
  import { SelectionService } from 'browser/services/SelectionService';
42
- import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, ILinkProviderService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
42
+ import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
43
43
  import { ThemeService } from 'browser/services/ThemeService';
44
+ import { KeyboardService } from 'browser/services/KeyboardService';
44
45
  import { channels, color } from 'common/Color';
45
46
  import { CoreTerminal } from 'common/CoreTerminal';
46
47
  import * as Browser from 'common/Platform';
@@ -48,7 +49,6 @@ import { ColorRequestType, CoreMouseAction, CoreMouseButton, CoreMouseEventType,
48
49
  import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
49
50
  import { IBuffer } from 'common/buffer/Types';
50
51
  import { C0, C1_ESCAPED } from 'common/data/EscapeSequences';
51
- import { evaluateKeyboardEvent } from 'common/input/Keyboard';
52
52
  import { toRgbString } from 'common/input/XParseColor';
53
53
  import { DecorationService } from 'common/services/DecorationService';
54
54
  import { IDecorationService } from 'common/services/Services';
@@ -80,8 +80,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
80
80
  private _customWheelEventHandler: CustomWheelEventHandler | undefined;
81
81
 
82
82
  // Browser services
83
- private _decorationService: DecorationService;
84
- private _linkProviderService: ILinkProviderService;
83
+ private readonly _decorationService: DecorationService;
84
+ private readonly _keyboardService: IKeyboardService;
85
+ private readonly _linkProviderService: ILinkProviderService;
85
86
 
86
87
  // Optional browser services
87
88
  private _charSizeService: ICharSizeService | undefined;
@@ -173,6 +174,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
173
174
 
174
175
  this._decorationService = this._instantiationService.createInstance(DecorationService);
175
176
  this._instantiationService.setService(IDecorationService, this._decorationService);
177
+ this._keyboardService = this._instantiationService.createInstance(KeyboardService);
178
+ this._instantiationService.setService(IKeyboardService, this._keyboardService);
176
179
  this._linkProviderService = this._instantiationService.createInstance(LinkProviderService);
177
180
  this._instantiationService.setService(ILinkProviderService, this._linkProviderService);
178
181
  this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(OscLinkProvider));
@@ -1081,7 +1084,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
1081
1084
  this._unprocessedDeadKey = true;
1082
1085
  }
1083
1086
 
1084
- const result = evaluateKeyboardEvent(event, this.coreService.decPrivateModes.applicationCursorKeys, this.browser.isMac, this.options.macOptionIsMeta);
1087
+ const result = this._keyboardService.evaluateKeyDown(event);
1085
1088
 
1086
1089
  this.updateCursorStyle(event);
1087
1090
 
@@ -1109,8 +1112,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
1109
1112
  }
1110
1113
 
1111
1114
  // HACK: Process A-Z in the keypress event to fix an issue with macOS IMEs where lower case
1112
- // letters cannot be input while caps lock is on.
1113
- if (event.key && !event.ctrlKey && !event.altKey && !event.metaKey && event.key.length === 1) {
1115
+ // letters cannot be input while caps lock is on. Skip this hack when using kitty protocol
1116
+ // as it needs to send proper CSI u sequences for all key events.
1117
+ if (!this._keyboardService.useKitty && event.key && !event.ctrlKey && !event.altKey && !event.metaKey && event.key.length === 1) {
1114
1118
  if (event.key.charCodeAt(0) >= 65 && event.key.charCodeAt(0) <= 90) {
1115
1119
  return true;
1116
1120
  }
@@ -1168,6 +1172,12 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
1168
1172
  this.focus();
1169
1173
  }
1170
1174
 
1175
+ // Handle key release for Kitty keyboard protocol
1176
+ const result = this._keyboardService.evaluateKeyUp(ev);
1177
+ if (result?.key) {
1178
+ this.coreService.triggerDataEvent(result.key, true);
1179
+ }
1180
+
1171
1181
  this.updateCursorStyle(ev);
1172
1182
  this._keyPressHandled = false;
1173
1183
  }
@@ -0,0 +1,41 @@
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 { evaluateKeyboardEventKitty, KittyKeyboardEventType, KittyKeyboardFlags, shouldUseKittyProtocol } from 'common/input/KittyKeyboard';
9
+ import { isMac } from 'common/Platform';
10
+ import { ICoreService, IOptionsService } from 'common/services/Services';
11
+ import { IKeyboardResult } from 'common/Types';
12
+
13
+ export class KeyboardService implements IKeyboardService {
14
+ public serviceBrand: undefined;
15
+
16
+ constructor(
17
+ @ICoreService private readonly _coreService: ICoreService,
18
+ @IOptionsService private readonly _optionsService: IOptionsService
19
+ ) {
20
+ }
21
+
22
+ public evaluateKeyDown(event: KeyboardEvent): IKeyboardResult {
23
+ const kittyFlags = this._coreService.kittyKeyboard.flags;
24
+ return this.useKitty
25
+ ? evaluateKeyboardEventKitty(event, kittyFlags, event.repeat ? KittyKeyboardEventType.REPEAT : KittyKeyboardEventType.PRESS)
26
+ : evaluateKeyboardEvent(event, this._coreService.decPrivateModes.applicationCursorKeys, isMac, this._optionsService.rawOptions.macOptionIsMeta);
27
+ }
28
+
29
+ public evaluateKeyUp(event: KeyboardEvent): IKeyboardResult | undefined {
30
+ const kittyFlags = this._coreService.kittyKeyboard.flags;
31
+ if (this.useKitty && (kittyFlags & KittyKeyboardFlags.REPORT_EVENT_TYPES)) {
32
+ return evaluateKeyboardEventKitty(event, kittyFlags, KittyKeyboardEventType.RELEASE);
33
+ }
34
+ return undefined;
35
+ }
36
+
37
+ public get useKitty(): boolean {
38
+ const kittyFlags = this._coreService.kittyKeyboard.flags;
39
+ return !!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard && shouldUseKittyProtocol(kittyFlags));
40
+ }
41
+ }
@@ -7,7 +7,7 @@ import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';
7
7
  import { IColorSet, ILink, ReadonlyColorSet } from 'browser/Types';
8
8
  import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
9
9
  import { createDecorator } from 'common/services/ServiceRegistry';
10
- import { AllColorIndex, IDisposable } from 'common/Types';
10
+ import { AllColorIndex, IDisposable, IKeyboardResult } from 'common/Types';
11
11
  import type { Event } from 'vs/base/common/event';
12
12
 
13
13
  export const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');
@@ -156,3 +156,11 @@ export interface ILinkProviderService extends IDisposable {
156
156
  export interface ILinkProvider {
157
157
  provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void;
158
158
  }
159
+
160
+ export const IKeyboardService = createDecorator<IKeyboardService>('KeyboardService');
161
+ export interface IKeyboardService {
162
+ serviceBrand: undefined;
163
+ evaluateKeyDown(event: KeyboardEvent): IKeyboardResult;
164
+ evaluateKeyUp(event: KeyboardEvent): IKeyboardResult | undefined;
165
+ readonly useKitty: boolean;
166
+ }
@@ -270,6 +270,12 @@ export class InputHandler extends Disposable implements IInputHandler {
270
270
  this._parser.registerCsiHandler({ intermediates: '$', final: 'p' }, params => this.requestMode(params, true));
271
271
  this._parser.registerCsiHandler({ prefix: '?', intermediates: '$', final: 'p' }, params => this.requestMode(params, false));
272
272
 
273
+ // Kitty keyboard protocol handlers
274
+ this._parser.registerCsiHandler({ prefix: '=', final: 'u' }, params => this.kittyKeyboardSet(params));
275
+ this._parser.registerCsiHandler({ prefix: '?', final: 'u' }, params => this.kittyKeyboardQuery(params));
276
+ this._parser.registerCsiHandler({ prefix: '>', final: 'u' }, params => this.kittyKeyboardPush(params));
277
+ this._parser.registerCsiHandler({ prefix: '<', final: 'u' }, params => this.kittyKeyboardPop(params));
278
+
273
279
  /**
274
280
  * execute handler
275
281
  */
@@ -2003,6 +2009,12 @@ export class InputHandler extends Disposable implements IInputHandler {
2003
2009
  // FALL-THROUGH
2004
2010
  case 47: // alt screen buffer
2005
2011
  case 1047: // alt screen buffer
2012
+ // Swap kitty keyboard flags: save main, restore alt
2013
+ if (this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {
2014
+ const state = this._coreService.kittyKeyboard;
2015
+ state.mainFlags = state.flags;
2016
+ state.flags = state.altFlags;
2017
+ }
2006
2018
  this._bufferService.buffers.activateAltBuffer(this._eraseAttrData());
2007
2019
  this._coreService.isCursorInitialized = true;
2008
2020
  this._onRequestRefreshRows.fire(undefined);
@@ -2232,6 +2244,12 @@ export class InputHandler extends Disposable implements IInputHandler {
2232
2244
  // FALL-THROUGH
2233
2245
  case 47: // normal screen buffer
2234
2246
  case 1047: // normal screen buffer - clearing it first
2247
+ // Swap kitty keyboard flags: save alt, restore main
2248
+ if (this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {
2249
+ const state = this._coreService.kittyKeyboard;
2250
+ state.altFlags = state.flags;
2251
+ state.flags = state.mainFlags;
2252
+ }
2235
2253
  // Ensure the selection manager has the correct buffer
2236
2254
  this._bufferService.buffers.activateNormalBuffer();
2237
2255
  if (params.params[i] === 1049) {
@@ -2654,10 +2672,10 @@ export class InputHandler extends Disposable implements IInputHandler {
2654
2672
  } else if (p === 55) {
2655
2673
  // not overline
2656
2674
  attr.bg &= ~BgFlags.OVERLINE;
2657
- } else if (p === 221) {
2675
+ } else if (p === 221 && (this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl ?? true)) {
2658
2676
  // not bold (kitty extension)
2659
2677
  attr.fg &= ~FgFlags.BOLD;
2660
- } else if (p === 222) {
2678
+ } else if (p === 222 && (this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl ?? true)) {
2661
2679
  // not faint (kitty extension)
2662
2680
  attr.bg &= ~BgFlags.DIM;
2663
2681
  } else if (p === 59) {
@@ -2984,7 +3002,6 @@ export class InputHandler extends Disposable implements IInputHandler {
2984
3002
  return true;
2985
3003
  }
2986
3004
 
2987
-
2988
3005
  /**
2989
3006
  * OSC 2; <data> ST (set window title)
2990
3007
  * Proxy to set window title.
@@ -3496,6 +3513,107 @@ export class InputHandler extends Disposable implements IInputHandler {
3496
3513
  public markRangeDirty(y1: number, y2: number): void {
3497
3514
  this._dirtyRowTracker.markRangeDirty(y1, y2);
3498
3515
  }
3516
+
3517
+ // #region Kitty keyboard
3518
+
3519
+ /**
3520
+ * CSI = flags ; mode u
3521
+ * Set Kitty keyboard protocol flags.
3522
+ * mode: 1=set, 2=set-only-specified, 3=reset-only-specified
3523
+ *
3524
+ * @vt: #Y CSI KKBDSET "Kitty Keyboard Set" "CSI = Ps ; Pm u" "Set Kitty keyboard protocol flags."
3525
+ */
3526
+ public kittyKeyboardSet(params: IParams): boolean {
3527
+ if (!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {
3528
+ return true;
3529
+ }
3530
+ const flags = params.params[0] || 0;
3531
+ const mode = params.params[1] || 1;
3532
+ const state = this._coreService.kittyKeyboard;
3533
+
3534
+ switch (mode) {
3535
+ case 1: // Set all flags
3536
+ state.flags = flags;
3537
+ break;
3538
+ case 2: // Set only specified flags (OR)
3539
+ state.flags |= flags;
3540
+ break;
3541
+ case 3: // Reset only specified flags (AND NOT)
3542
+ state.flags &= ~flags;
3543
+ break;
3544
+ }
3545
+ return true;
3546
+ }
3547
+
3548
+ /**
3549
+ * CSI ? u
3550
+ * Query Kitty keyboard protocol flags.
3551
+ * Terminal responds with CSI ? flags u
3552
+ *
3553
+ * @vt: #Y CSI KKBDQUERY "Kitty Keyboard Query" "CSI ? u" "Query Kitty keyboard protocol flags."
3554
+ */
3555
+ public kittyKeyboardQuery(params: IParams): boolean {
3556
+ if (!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {
3557
+ return true;
3558
+ }
3559
+ const flags = this._coreService.kittyKeyboard.flags;
3560
+ this._coreService.triggerDataEvent(`${C0.ESC}[?${flags}u`);
3561
+ return true;
3562
+ }
3563
+
3564
+ /**
3565
+ * CSI > flags u
3566
+ * Push Kitty keyboard flags onto stack and set new flags.
3567
+ *
3568
+ * @vt: #Y CSI KKBDPUSH "Kitty Keyboard Push" "CSI > Ps u" "Push keyboard flags to stack and set new flags."
3569
+ */
3570
+ public kittyKeyboardPush(params: IParams): boolean {
3571
+ if (!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {
3572
+ return true;
3573
+ }
3574
+ const flags = params.params[0] || 0;
3575
+ const state = this._coreService.kittyKeyboard;
3576
+ const isAlt = this._bufferService.buffer === this._bufferService.buffers.alt;
3577
+ const stack = isAlt ? state.altStack : state.mainStack;
3578
+
3579
+ // Evict oldest entry if stack is full (DoS protection, limit of 16)
3580
+ if (stack.length >= 16) {
3581
+ stack.shift();
3582
+ }
3583
+
3584
+ // Push current flags onto stack and set new flags
3585
+ stack.push(state.flags);
3586
+ state.flags = flags;
3587
+ return true;
3588
+ }
3589
+
3590
+ /**
3591
+ * CSI < count u
3592
+ * Pop Kitty keyboard flags from stack.
3593
+ *
3594
+ * @vt: #Y CSI KKBDPOP "Kitty Keyboard Pop" "CSI < Ps u" "Pop keyboard flags from stack."
3595
+ */
3596
+ public kittyKeyboardPop(params: IParams): boolean {
3597
+ if (!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard) {
3598
+ return true;
3599
+ }
3600
+ const count = Math.max(1, params.params[0] || 1);
3601
+ const state = this._coreService.kittyKeyboard;
3602
+ const isAlt = this._bufferService.buffer === this._bufferService.buffers.alt;
3603
+ const stack = isAlt ? state.altStack : state.mainStack;
3604
+
3605
+ // Pop specified number of entries from stack
3606
+ for (let i = 0; i < count && stack.length > 0; i++) {
3607
+ state.flags = stack.pop()!;
3608
+ }
3609
+ // If stack is empty after popping, reset to 0
3610
+ if (stack.length === 0 && count > 0) {
3611
+ state.flags = 0;
3612
+ }
3613
+ return true;
3614
+ }
3615
+
3616
+ // #endregion
3499
3617
  }
3500
3618
 
3501
3619
  export interface IDirtyRowTracker {
@@ -277,6 +277,23 @@ export interface IDecPrivateModes {
277
277
  wraparound: boolean; // defaults: xterm - true, vt100 - false
278
278
  }
279
279
 
280
+ /**
281
+ * Kitty keyboard protocol state.
282
+ * Maintains per-screen stacks of enhancement flags.
283
+ */
284
+ export interface IKittyKeyboardState {
285
+ /** Current active enhancement flags (for current screen) */
286
+ flags: number;
287
+ /** Saved flags for main screen when alt is active */
288
+ mainFlags: number;
289
+ /** Saved flags for alternate screen when main is active */
290
+ altFlags: number;
291
+ /** Stack of flags for main screen */
292
+ mainStack: number[];
293
+ /** Stack of flags for alternate screen */
294
+ altStack: number[];
295
+ }
296
+
280
297
  export interface IRowRange {
281
298
  start: number;
282
299
  end: number;
@@ -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.98';
9
+ export const XTERM_VERSION = '6.1.0-beta.99';