From ec78500f869dbde44ae986c70ad742203dbe3ed0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 10 Jan 2026 04:19:23 -0800 Subject: [PATCH] Create IKeyboardService --- src/browser/CoreBrowserTerminal.ts | 27 ++++++---------- src/browser/services/KeyboardService.ts | 41 +++++++++++++++++++++++++ src/browser/services/Services.ts | 10 +++++- 3 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 src/browser/services/KeyboardService.ts diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index 0f452aff..27e309d1 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -39,8 +39,9 @@ import { LinkProviderService } from 'browser/services/LinkProviderService'; import { MouseService } from 'browser/services/MouseService'; import { RenderService } from 'browser/services/RenderService'; import { SelectionService } from 'browser/services/SelectionService'; -import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, ILinkProviderService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services'; +import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services'; import { ThemeService } from 'browser/services/ThemeService'; +import { KeyboardService } from 'browser/services/KeyboardService'; import { channels, color } from 'common/Color'; import { CoreTerminal } from 'common/CoreTerminal'; import * as Browser from 'common/Platform'; @@ -48,8 +49,6 @@ import { ColorRequestType, CoreMouseAction, CoreMouseButton, CoreMouseEventType, import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBuffer } from 'common/buffer/Types'; import { C0, C1_ESCAPED } from 'common/data/EscapeSequences'; -import { evaluateKeyboardEvent } from 'common/input/Keyboard'; -import { evaluateKeyboardEventKitty, KittyKeyboardEventType, shouldUseKittyProtocol } from 'common/input/KittyKeyboard'; import { toRgbString } from 'common/input/XParseColor'; import { DecorationService } from 'common/services/DecorationService'; import { IDecorationService } from 'common/services/Services'; @@ -82,6 +81,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { // Browser services private _decorationService: DecorationService; + private _keyboardService: IKeyboardService; private _linkProviderService: ILinkProviderService; // Optional browser services @@ -174,6 +174,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { this._decorationService = this._instantiationService.createInstance(DecorationService); this._instantiationService.setService(IDecorationService, this._decorationService); + this._keyboardService = this._instantiationService.createInstance(KeyboardService); + this._instantiationService.setService(IKeyboardService, this._keyboardService); this._linkProviderService = this._instantiationService.createInstance(LinkProviderService); this._instantiationService.setService(ILinkProviderService, this._linkProviderService); this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(OscLinkProvider)); @@ -1082,12 +1084,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { this._unprocessedDeadKey = true; } - // Use Kitty keyboard protocol if enabled, otherwise use legacy encoding - const kittyFlags = this.coreService.kittyKeyboard.flags; - const useKitty = this.options.vtExtensions?.kittyKeyboard && shouldUseKittyProtocol(kittyFlags); - const result = useKitty - ? evaluateKeyboardEventKitty(event, kittyFlags, event.repeat ? KittyKeyboardEventType.REPEAT : KittyKeyboardEventType.PRESS) - : evaluateKeyboardEvent(event, this.coreService.decPrivateModes.applicationCursorKeys, this.browser.isMac, this.options.macOptionIsMeta); + const result = this._keyboardService.evaluateKeyDown(event); this.updateCursorStyle(event); @@ -1117,7 +1114,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { // HACK: Process A-Z in the keypress event to fix an issue with macOS IMEs where lower case // letters cannot be input while caps lock is on. Skip this hack when using kitty protocol // as it needs to send proper CSI u sequences for all key events. - if (!useKitty && event.key && !event.ctrlKey && !event.altKey && !event.metaKey && event.key.length === 1) { + if (!this._keyboardService.useKitty && event.key && !event.ctrlKey && !event.altKey && !event.metaKey && event.key.length === 1) { if (event.key.charCodeAt(0) >= 65 && event.key.charCodeAt(0) <= 90) { return true; } @@ -1176,13 +1173,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { } // Handle key release for Kitty keyboard protocol - const kittyFlags = this.coreService.kittyKeyboard.flags; - const useKitty = this.options.vtExtensions?.kittyKeyboard && shouldUseKittyProtocol(kittyFlags); - if (useKitty && (kittyFlags & 0b10)) { // REPORT_EVENT_TYPES flag - const result = evaluateKeyboardEventKitty(ev, kittyFlags, KittyKeyboardEventType.RELEASE); - if (result.key) { - this.coreService.triggerDataEvent(result.key, true); - } + const result = this._keyboardService.evaluateKeyUp(ev); + if (result?.key) { + this.coreService.triggerDataEvent(result.key, true); } this.updateCursorStyle(ev); diff --git a/src/browser/services/KeyboardService.ts b/src/browser/services/KeyboardService.ts new file mode 100644 index 00000000..c1603300 --- /dev/null +++ b/src/browser/services/KeyboardService.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2025 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IKeyboardService } from 'browser/services/Services'; +import { evaluateKeyboardEvent } from 'common/input/Keyboard'; +import { evaluateKeyboardEventKitty, KittyKeyboardEventType, shouldUseKittyProtocol } from 'common/input/KittyKeyboard'; +import { isMac } from 'common/Platform'; +import { ICoreService, IOptionsService } from 'common/services/Services'; +import { IKeyboardResult } from 'common/Types'; + +export class KeyboardService implements IKeyboardService { + public serviceBrand: undefined; + + constructor( + @ICoreService private readonly _coreService: ICoreService, + @IOptionsService private readonly _optionsService: IOptionsService + ) { + } + + public evaluateKeyDown(event: KeyboardEvent): IKeyboardResult { + const kittyFlags = this._coreService.kittyKeyboard.flags; + return this.useKitty + ? evaluateKeyboardEventKitty(event, kittyFlags, event.repeat ? KittyKeyboardEventType.REPEAT : KittyKeyboardEventType.PRESS) + : evaluateKeyboardEvent(event, this._coreService.decPrivateModes.applicationCursorKeys, isMac, this._optionsService.rawOptions.macOptionIsMeta); + } + + public evaluateKeyUp(event: KeyboardEvent): IKeyboardResult | undefined { + const kittyFlags = this._coreService.kittyKeyboard.flags; + if (this.useKitty && (kittyFlags & 0b10)) { // REPORT_EVENT_TYPES flag + return evaluateKeyboardEventKitty(event, kittyFlags, KittyKeyboardEventType.RELEASE); + } + return undefined; + } + + public get useKitty(): boolean { + const kittyFlags = this._coreService.kittyKeyboard.flags; + return !!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard && shouldUseKittyProtocol(kittyFlags)); + } +} diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 6103ada1..cac62915 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -7,7 +7,7 @@ import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types'; import { IColorSet, ILink, ReadonlyColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; -import { AllColorIndex, IDisposable } from 'common/Types'; +import { AllColorIndex, IDisposable, IKeyboardResult } from 'common/Types'; import type { Event } from 'vs/base/common/event'; export const ICharSizeService = createDecorator('CharSizeService'); @@ -156,3 +156,11 @@ export interface ILinkProviderService extends IDisposable { export interface ILinkProvider { provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void; } + +export const IKeyboardService = createDecorator('KeyboardService'); +export interface IKeyboardService { + serviceBrand: undefined; + evaluateKeyDown(event: KeyboardEvent): IKeyboardResult; + evaluateKeyUp(event: KeyboardEvent): IKeyboardResult | undefined; + readonly useKitty: boolean; +}