From c8dddddbbbd065db8d107d03b02249f0b3a0c634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 10 May 2026 16:45:46 +0200 Subject: [PATCH 1/5] remove js-base64 dependency --- addons/addon-clipboard/package.json | 3 --- addons/addon-clipboard/src/ClipboardAddon.ts | 12 +++++------- addons/addon-clipboard/test/ClipboardAddon.test.ts | 2 +- package-lock.json | 10 +--------- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/addons/addon-clipboard/package.json b/addons/addon-clipboard/package.json index 176fa861a..eb207679d 100644 --- a/addons/addon-clipboard/package.json +++ b/addons/addon-clipboard/package.json @@ -21,8 +21,5 @@ "package": "../../node_modules/.bin/webpack", "prepublishOnly": "npm run package", "start": "node ../../demo/start" - }, - "dependencies": { - "js-base64": "^3.7.5" } } diff --git a/addons/addon-clipboard/src/ClipboardAddon.ts b/addons/addon-clipboard/src/ClipboardAddon.ts index 58425924f..123edfec5 100644 --- a/addons/addon-clipboard/src/ClipboardAddon.ts +++ b/addons/addon-clipboard/src/ClipboardAddon.ts @@ -5,7 +5,6 @@ import type { IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm'; import { type IClipboardProvider, ClipboardSelectionType, type IBase64 } from '@xterm/addon-clipboard'; -import { Base64 as JSBase64 } from 'js-base64'; export class ClipboardAddon implements ITerminalAddon { private _terminal?: Terminal; @@ -87,13 +86,12 @@ export class BrowserClipboardProvider implements IClipboardProvider { export class Base64 implements IBase64 { public encodeText(data: string): string { - return JSBase64.encode(data); + return btoa(data); } public decodeText(data: string): string { - const text = JSBase64.decode(data); - if (!JSBase64.isValid(data) || JSBase64.encode(text) !== data) { - return ''; - } - return text; + try { + return atob(data); + } catch (e) {} + return ''; } } diff --git a/addons/addon-clipboard/test/ClipboardAddon.test.ts b/addons/addon-clipboard/test/ClipboardAddon.test.ts index d4d86d46b..fc14fc75c 100644 --- a/addons/addon-clipboard/test/ClipboardAddon.test.ts +++ b/addons/addon-clipboard/test/ClipboardAddon.test.ts @@ -20,7 +20,7 @@ test.describe('ClipboardAddon', () => { test.beforeEach(async ({}, testInfo) => { // DEBT: This test doesn't work since the migration to @playwright/test - if (ctx.browser.browserType().name() !== 'chromium') { + if (ctx.browser.browserType().name() === 'webkit') { testInfo.skip(); return; } diff --git a/package-lock.json b/package-lock.json index ebe3acbef..23f9bbc2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,10 +64,7 @@ "addons/addon-clipboard": { "name": "@xterm/addon-clipboard", "version": "0.2.0", - "license": "MIT", - "dependencies": { - "js-base64": "^3.7.5" - } + "license": "MIT" }, "addons/addon-fit": { "name": "@xterm/addon-fit", @@ -5420,11 +5417,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-base64": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", - "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", From 6f5515048569c12fe8e1de9255d4324b1a77088d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 10 May 2026 17:06:08 +0200 Subject: [PATCH 2/5] make linter happy --- addons/addon-clipboard/src/ClipboardAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/addon-clipboard/src/ClipboardAddon.ts b/addons/addon-clipboard/src/ClipboardAddon.ts index 123edfec5..9e753a1a8 100644 --- a/addons/addon-clipboard/src/ClipboardAddon.ts +++ b/addons/addon-clipboard/src/ClipboardAddon.ts @@ -91,7 +91,7 @@ export class Base64 implements IBase64 { public decodeText(data: string): string { try { return atob(data); - } catch (e) {} + } catch {} return ''; } } From c5a11220c139ae83511a41e99cf1422083f1673f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 10 May 2026 18:00:26 +0200 Subject: [PATCH 3/5] always refer to navigator.clipboard in default provider --- addons/addon-clipboard/src/ClipboardAddon.ts | 16 +++------ .../test/ClipboardAddon.test.ts | 19 ++++++++++- .../typings/addon-clipboard.d.ts | 33 +++++++------------ 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/addons/addon-clipboard/src/ClipboardAddon.ts b/addons/addon-clipboard/src/ClipboardAddon.ts index 9e753a1a8..5e194880c 100644 --- a/addons/addon-clipboard/src/ClipboardAddon.ts +++ b/addons/addon-clipboard/src/ClipboardAddon.ts @@ -4,7 +4,7 @@ */ import type { IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm'; -import { type IClipboardProvider, ClipboardSelectionType, type IBase64 } from '@xterm/addon-clipboard'; +import { type IClipboardProvider, type IBase64 } from '@xterm/addon-clipboard'; export class ClipboardAddon implements ITerminalAddon { private _terminal?: Terminal; @@ -24,7 +24,7 @@ export class ClipboardAddon implements ITerminalAddon { return this._disposable?.dispose(); } - private _readText(sel: ClipboardSelectionType, data: string): void { + private _readText(sel: string, data: string): void { const b64 = this._base64.encodeText(data); this._terminal?.input(`\x1b]52;${sel};${b64}\x07`, false); } @@ -35,7 +35,7 @@ export class ClipboardAddon implements ITerminalAddon { return true; } - const pc = args[0] as ClipboardSelectionType; + const pc = args[0]; const pd = args[1]; if (pd === '?') { const text = this._provider.readText(pc); @@ -69,17 +69,11 @@ export class ClipboardAddon implements ITerminalAddon { } export class BrowserClipboardProvider implements IClipboardProvider { - public async readText(selection: ClipboardSelectionType): Promise { - if (selection !== 'c') { - return Promise.resolve(''); - } + public readText(selection: string): Promise { return navigator.clipboard.readText(); } - public async writeText(selection: ClipboardSelectionType, text: string): Promise { - if (selection !== 'c') { - return Promise.resolve(); - } + public writeText(selection: string, text: string): Promise { return navigator.clipboard.writeText(text); } } diff --git a/addons/addon-clipboard/test/ClipboardAddon.test.ts b/addons/addon-clipboard/test/ClipboardAddon.test.ts index fc14fc75c..2c04e619b 100644 --- a/addons/addon-clipboard/test/ClipboardAddon.test.ts +++ b/addons/addon-clipboard/test/ClipboardAddon.test.ts @@ -48,6 +48,14 @@ test.describe('ClipboardAddon', () => { await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`); deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded); }); + test('primary selection', async () => { + await ctx.proxy.write(`\x1b]52;p;${testDataEncoded}\x07`); + deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded); + }); + test('empty selection (default)', async () => { + await ctx.proxy.write(`\x1b]52;;${testDataEncoded}\x07`); + deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded); + }); test('invalid base64 string', async () => { await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}invalid\x07`); deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), ''); @@ -60,15 +68,24 @@ test.describe('ClipboardAddon', () => { }); test.describe('read data', async function (): Promise { - test('simple string', async () => { + test.beforeAll(async () => { await ctx.page.evaluate(` window.data = []; window.term.onData(e => data.push(e)); `); + }); + test('simple string', async () => { + await ctx.page.evaluate('window.data.length = 0;'); await ctx.page.evaluate(() => window.navigator.clipboard.writeText('hello world')); await ctx.proxy.write(`\x1b]52;c;?\x07`); deepEqual(await ctx.page.evaluate('window.data'), [`\x1b]52;c;${testDataEncoded}\x07`]); }); + test('primary selection', async () => { + await ctx.page.evaluate('window.data.length = 0;'); + await ctx.page.evaluate(() => window.navigator.clipboard.writeText('hello world')); + await ctx.proxy.write(`\x1b]52;p;?\x07`); + deepEqual(await ctx.page.evaluate('window.data'), [`\x1b]52;p;${testDataEncoded}\x07`]); + }); test('clear clipboard', async () => { await ctx.proxy.write(`\x1b]52;c;!\x07`); await ctx.proxy.write(`\x1b]52;c;?\x07`); diff --git a/addons/addon-clipboard/typings/addon-clipboard.d.ts b/addons/addon-clipboard/typings/addon-clipboard.d.ts index 4ef12e0f7..f8e024ac3 100644 --- a/addons/addon-clipboard/typings/addon-clipboard.d.ts +++ b/addons/addon-clipboard/typings/addon-clipboard.d.ts @@ -30,18 +30,6 @@ declare module '@xterm/addon-clipboard' { public dispose(): void } - /** - * Clipboard selection type. This is used to specify which selection buffer to - * read or write to. - * - SYSTEM `c`: The system clipboard. - * - PRIMARY `p`: The primary clipboard. This is provided for compatibility - * with Linux X11. - */ - export const enum ClipboardSelectionType { - SYSTEM = 'c', - PRIMARY = 'p', - } - export interface IBase64 { /** * Converts a utf-8 string to a base64 string. @@ -78,36 +66,39 @@ declare module '@xterm/addon-clipboard' { export interface IClipboardProvider { /** * Gets the clipboard content. - * @param selection The clipboard selection to read. + * @param selection The clipboard selection to read (see OSC 52 spec of xterm). * @returns A promise that resolves with clipboard selection data. */ - readText(selection: ClipboardSelectionType): string | Promise; + readText(selection: string): string | Promise; /** * Sets the clipboard content. - * @param selection The clipboard selection to set. + * @param selection The clipboard selection to set (see OSC 52 spec of xterm). * @param data The clipboard text to write. */ - writeText(selection: ClipboardSelectionType, text: string): void | Promise; + writeText(selection: string, text: string): void | Promise; } /** - * The clipboard provider interface that enables xterm.js to access the system clipboard. + * The clipboard provider interface that enables xterm.js to access the browser clipboard. + * + * Note this this clipboard provider does not implement own cut buffers as xterm does, + * but redirect the selection parameter always to navigator.clipboard. */ export class BrowserClipboardProvider implements IClipboardProvider{ /** * Reads text from the clipboard. - * @param selection The selection type to read from. + * @param selection The selection type to read from (always refers to navigator.clipboard). * @returns A promise that resolves with the text from the clipboard. */ - public readText(selection: ClipboardSelectionType): Promise; + public readText(selection: string): Promise; /** * Writes text to the clipboard. - * @param selection The selection type to write to. + * @param selection The selection type to write to (always refers to navigator.clipboard). * @param data The text to write to the clipboard. * @returns A promise that resolves when the text has been written to the clipboard. */ - public writeText(selection: ClipboardSelectionType, data: string): Promise; + public writeText(selection: string, data: string): Promise; } } From b0f5bc2d280969d65bb2ae6a15e96a42ea5427b9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 2 Apr 2026 13:01:55 +0800 Subject: [PATCH 4/5] Fix kitty keyboard "Report event types" behavior not matching spec According to spec: - The Enter, Tab and Backspace keys will not have release events unless "Report all keys as escape codes" is also set. - Key events that result in text are reported as plain UTF-8 text, so events are not supported for them, unless the application requests "key report" mode. I checked kitty's behavior. While it does report release events for keys that result in text as CSI sequences, press and repeat events for these are reported as plain UTF-8 text. This commit changes Xterm.js to match this behavior. --- src/common/input/KittyKeyboard.test.ts | 117 ++++++++++++++++++++++++- src/common/input/KittyKeyboard.ts | 7 +- 2 files changed, 119 insertions(+), 5 deletions(-) diff --git a/src/common/input/KittyKeyboard.test.ts b/src/common/input/KittyKeyboard.test.ts index b5aa9d643..7646c052b 100644 --- a/src/common/input/KittyKeyboard.test.ts +++ b/src/common/input/KittyKeyboard.test.ts @@ -453,9 +453,29 @@ describe('KittyKeyboard', () => { describe('event types (press/repeat/release)', () => { const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES | KittyKeyboardFlags.REPORT_EVENT_TYPES; - it('press event (default, no suffix)', () => { + it('UTF-8 text press event', () => { const result = kitty.evaluate(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.PRESS); - assert.strictEqual(result.key, '\x1b[97u'); + assert.strictEqual(result.key, 'a'); + }); + + it('Escape key press event (default, no suffix)', () => { + const result = kitty.evaluate(createEvent({ key: 'Escape' }), flags, KittyKeyboardEventType.PRESS); + assert.strictEqual(result.key, '\x1b[27u'); + }); + + it('Enter key press event → legacy \\r', () => { + const result = kitty.evaluate(createEvent({ key: 'Enter' }), flags, KittyKeyboardEventType.PRESS); + assert.strictEqual(result.key, '\r'); + }); + + it('Tab key press event → legacy \\t', () => { + const result = kitty.evaluate(createEvent({ key: 'Tab' }), flags, KittyKeyboardEventType.PRESS); + assert.strictEqual(result.key, '\t'); + }); + + it('Backspace key press event → legacy \\x7f', () => { + const result = kitty.evaluate(createEvent({ key: 'Backspace' }), flags, KittyKeyboardEventType.PRESS); + assert.strictEqual(result.key, '\x7f'); }); it('press event explicit :1 when modifiers present', () => { @@ -463,9 +483,29 @@ describe('KittyKeyboard', () => { assert.strictEqual(result.key, '\x1b[97;5u'); }); - it('repeat event → :2 suffix', () => { + it('UTF-8 text repeat event', () => { const result = kitty.evaluate(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.REPEAT); - assert.strictEqual(result.key, '\x1b[97;1:2u'); + assert.strictEqual(result.key, 'a'); + }); + + it('Escape key repeat event → :2 suffix', () => { + const result = kitty.evaluate(createEvent({ key: 'Escape' }), flags, KittyKeyboardEventType.REPEAT); + assert.strictEqual(result.key, '\x1b[27;1:2u'); + }); + + it('Enter key repeat event → legacy \\r', () => { + const result = kitty.evaluate(createEvent({ key: 'Enter' }), flags, KittyKeyboardEventType.REPEAT); + assert.strictEqual(result.key, '\r'); + }); + + it('Tab key repeat event → legacy \\t', () => { + const result = kitty.evaluate(createEvent({ key: 'Tab' }), flags, KittyKeyboardEventType.REPEAT); + assert.strictEqual(result.key, '\t'); + }); + + it('Backspace key repeat event → legacy \\x7f', () => { + const result = kitty.evaluate(createEvent({ key: 'Backspace' }), flags, KittyKeyboardEventType.REPEAT); + assert.strictEqual(result.key, '\x7f'); }); it('release event → :3 suffix', () => { @@ -473,6 +513,26 @@ describe('KittyKeyboard', () => { assert.strictEqual(result.key, '\x1b[97;1:3u'); }); + it('Escape key release event → :3 suffix', () => { + const result = kitty.evaluate(createEvent({ key: 'Escape' }), flags, KittyKeyboardEventType.RELEASE); + assert.strictEqual(result.key, '\x1b[27;1:3u'); + }); + + it('Enter key release event is not reported', () => { + const result = kitty.evaluate(createEvent({ key: 'Enter' }), flags, KittyKeyboardEventType.RELEASE); + assert.strictEqual(result.key, undefined); + }); + + it('Tab key release event is not reported', () => { + const result = kitty.evaluate(createEvent({ key: 'Tab' }), flags, KittyKeyboardEventType.RELEASE); + assert.strictEqual(result.key, undefined); + }); + + it('Backspace key release event is not reported', () => { + const result = kitty.evaluate(createEvent({ key: 'Backspace' }), flags, KittyKeyboardEventType.RELEASE); + assert.strictEqual(result.key, undefined); + }); + it('release with modifier → mod:3', () => { const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true }), flags, KittyKeyboardEventType.RELEASE); assert.strictEqual(result.key, '\x1b[97;5:3u'); @@ -563,6 +623,55 @@ describe('KittyKeyboard', () => { }); }); + describe('REPORT_ALL_KEYS_AS_ESCAPE_CODES flag with REPORT_EVENT_TYPES', () => { + const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES | KittyKeyboardFlags.REPORT_EVENT_TYPES; + + it('Enter key press event → CSI 13 u', () => { + const result = kitty.evaluate(createEvent({ key: 'Enter' }), flags, KittyKeyboardEventType.PRESS); + assert.strictEqual(result.key, '\x1b[13u'); + }); + + it('Tab key press event → CSI 9 u', () => { + const result = kitty.evaluate(createEvent({ key: 'Tab' }), flags, KittyKeyboardEventType.PRESS); + assert.strictEqual(result.key, '\x1b[9u'); + }); + + it('Backspace key press event → CSI 127 u', () => { + const result = kitty.evaluate(createEvent({ key: 'Backspace' }), flags, KittyKeyboardEventType.PRESS); + assert.strictEqual(result.key, '\x1b[127u'); + }); + + it('Enter key repeat event → CSI 13;1:2 u', () => { + const result = kitty.evaluate(createEvent({ key: 'Enter' }), flags, KittyKeyboardEventType.REPEAT); + assert.strictEqual(result.key, '\x1b[13;1:2u'); + }); + + it('Tab key repeat event → CSI 9;1:2 u', () => { + const result = kitty.evaluate(createEvent({ key: 'Tab' }), flags, KittyKeyboardEventType.REPEAT); + assert.strictEqual(result.key, '\x1b[9;1:2u'); + }); + + it('Backspace key repeat event → CSI 127;1:2 u', () => { + const result = kitty.evaluate(createEvent({ key: 'Backspace' }), flags, KittyKeyboardEventType.REPEAT); + assert.strictEqual(result.key, '\x1b[127;1:2u'); + }); + + it('Enter key release event → CSI 13;1:3 u', () => { + const result = kitty.evaluate(createEvent({ key: 'Enter' }), flags, KittyKeyboardEventType.RELEASE); + assert.strictEqual(result.key, '\x1b[13;1:3u'); + }); + + it('Tab key release event → CSI 9;1:3 u', () => { + const result = kitty.evaluate(createEvent({ key: 'Tab' }), flags, KittyKeyboardEventType.RELEASE); + assert.strictEqual(result.key, '\x1b[9;1:3u'); + }); + + it('Backspace key release event → CSI 127;1:3 u', () => { + const result = kitty.evaluate(createEvent({ key: 'Backspace' }), flags, KittyKeyboardEventType.RELEASE); + assert.strictEqual(result.key, '\x1b[127;1:3u'); + }); + }); + describe('REPORT_ASSOCIATED_TEXT flag', () => { const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES | KittyKeyboardFlags.REPORT_ASSOCIATED_TEXT; diff --git a/src/common/input/KittyKeyboard.ts b/src/common/input/KittyKeyboard.ts index e34f47070..f138c6940 100644 --- a/src/common/input/KittyKeyboard.ts +++ b/src/common/input/KittyKeyboard.ts @@ -477,7 +477,12 @@ export class KittyKeyboard { if (flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES) { useCsiU = true; - } else if (reportEventTypes) { + } else if (reportEventTypes && eventType === KittyKeyboardEventType.RELEASE) { + // Per spec, Enter/Tab/Backspace will not have release events unless "Report all keys as + // escape codes" (which is handled by the branch above) is also set. + if (keyCode === 13 || keyCode === 9 || keyCode === 127) { + return result; + } useCsiU = true; } else if (flags & KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES) { // Per spec, Enter/Tab/Backspace "still generate the same bytes as in legacy From e0add7fe4b45b95b1a950953dd64bf803ae3eb71 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 11 May 2026 13:44:34 +0800 Subject: [PATCH 5/5] Move return earlier --- src/common/input/KittyKeyboard.ts | 51 +++++++++++++++---------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/common/input/KittyKeyboard.ts b/src/common/input/KittyKeyboard.ts index f138c6940..f35cfebd5 100644 --- a/src/common/input/KittyKeyboard.ts +++ b/src/common/input/KittyKeyboard.ts @@ -471,34 +471,33 @@ export class KittyKeyboard { return result; } + // Special handling for Enter/Tab/Backspace. + const specialKey = keyCode === 13 || keyCode === 9 || keyCode === 127; + + // Per spec, Enter/Tab/Backspace will not have release events unless "Report all keys as escape + // codes" is also set. + if (specialKey && eventType === KittyKeyboardEventType.RELEASE && !(flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES)) { + return result; + } + const isFunc = this._functionalKeyCodes[ev.key] !== undefined || this._getNumpadKeyCode(ev) !== undefined; - let useCsiU = false; - - if (flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES) { - useCsiU = true; - } else if (reportEventTypes && eventType === KittyKeyboardEventType.RELEASE) { - // Per spec, Enter/Tab/Backspace will not have release events unless "Report all keys as - // escape codes" (which is handled by the branch above) is also set. - if (keyCode === 13 || keyCode === 9 || keyCode === 127) { - return result; - } - useCsiU = true; - } else if (flags & KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES) { - // Per spec, Enter/Tab/Backspace "still generate the same bytes as in legacy - // mode" and consider space to be a text-generating key, so these skip the isFunc fast-path - // and only get CSI u when modifiers are present (handled below). - const isDisambiguateLegacy = keyCode === 13 || keyCode === 9 || keyCode === 127; - if (isFunc && !isDisambiguateLegacy) { - useCsiU = true; - } else if (modifiers > 0) { - if (ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.key.length === 1) { - useCsiU = false; - } else { - useCsiU = true; - } - } - } + const useCsiU = !!( + flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES || + (reportEventTypes && eventType === KittyKeyboardEventType.RELEASE) || + (flags & KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES && + ( + // Per spec, Enter/Tab/Backspace "still generate the same bytes as in legacy mode" and + // consider space to be a text-generating key, so these skip the isFunc fast-path and only + // get CSI u when modifiers are present (handled below). + (isFunc && !specialKey) || + ( + (modifiers > 0 && ev.key.length !== 1) || + modifiers - 1 > KittyKeyboardModifiers.SHIFT + ) + ) + ) + ); if (useCsiU) { result.key = this._buildCsiUSequence(ev, keyCode, modifiers, eventType, flags, isFunc, isMod);