diff --git a/src/common/input/KittyKeyboard.test.ts b/src/common/input/KittyKeyboard.test.ts index a0422ecb1..b0daf9a7c 100644 --- a/src/common/input/KittyKeyboard.test.ts +++ b/src/common/input/KittyKeyboard.test.ts @@ -506,6 +506,25 @@ describe('KittyKeyboard', () => { const result = kitty.evaluate(createEvent({ key: 'Shift', code: 'ShiftLeft', shiftKey: false }), flags, KittyKeyboardEventType.RELEASE); assert.strictEqual(result.key, undefined); }); + + it('does not report CapsLock press without REPORT_ALL_KEYS_AS_ESCAPE_CODES', () => { + assert.strictEqual(kitty.evaluate(createEvent({ key: 'CapsLock', code: 'CapsLock' }), KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES).key, undefined); + assert.strictEqual(kitty.evaluate(createEvent({ key: 'CapsLock', code: 'CapsLock' }), KittyKeyboardFlags.REPORT_EVENT_TYPES).key, undefined); + assert.strictEqual(kitty.evaluate(createEvent({ key: 'CapsLock', code: 'CapsLock' }), KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES | KittyKeyboardFlags.REPORT_EVENT_TYPES).key, undefined); + }); + + it('does not report NumLock press without REPORT_ALL_KEYS_AS_ESCAPE_CODES', () => { + assert.strictEqual(kitty.evaluate(createEvent({ key: 'NumLock', code: 'NumLock' }), KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES).key, undefined); + }); + + it('does not report ScrollLock press without REPORT_ALL_KEYS_AS_ESCAPE_CODES', () => { + assert.strictEqual(kitty.evaluate(createEvent({ key: 'ScrollLock', code: 'ScrollLock' }), KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES).key, undefined); + }); + + it('does not report CapsLock release without REPORT_ALL_KEYS_AS_ESCAPE_CODES', () => { + const result = kitty.evaluate(createEvent({ key: 'CapsLock', code: 'CapsLock' }), flags, KittyKeyboardEventType.RELEASE); + assert.strictEqual(result.key, undefined); + }); }); describe('REPORT_ALL_KEYS_AS_ESCAPE_CODES flag', () => { diff --git a/src/common/input/KittyKeyboard.ts b/src/common/input/KittyKeyboard.ts index 76c468cc4..f4bfb4ba0 100644 --- a/src/common/input/KittyKeyboard.ts +++ b/src/common/input/KittyKeyboard.ts @@ -265,6 +265,19 @@ export class KittyKeyboard { return ev.key === 'Shift' || ev.key === 'Control' || ev.key === 'Alt' || ev.key === 'Meta'; } + /** + * Check if a key is a lock key (CapsLock/NumLock/ScrollLock). + * + * Kitty's reference implementation classifies these as modifier keys for the + * purpose of suppressing press events (kitty/keys.c `is_modifier_key()` + * includes `GLFW_FKEY_CAPS_LOCK`, `GLFW_FKEY_SCROLL_LOCK`, `GLFW_FKEY_NUM_LOCK`), + * and its test suite asserts that a CapsLock press with no protocol flags + * produces empty output. + */ + private _isLockKey(ev: IKeyboardEvent): boolean { + return ev.key === 'CapsLock' || ev.key === 'NumLock' || ev.key === 'ScrollLock'; + } + /** * Build CSI letter sequence for arrow keys, Home, End. * Format: CSI [1;mod] letter @@ -422,6 +435,14 @@ export class KittyKeyboard { return result; } + // Spec § "Report all keys as escape codes": "Additionally, with this mode, + // events for pressing modifier keys are reported." — i.e. *without* this + // mode, modifier-key press events are suppressed. Kitty's is_modifier_key() + // treats CapsLock/NumLock/ScrollLock as modifier keys for this rule. + if (this._isLockKey(ev) && !(flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES)) { + return result; + } + const csiLetter = this._csiLetterKeys[ev.key]; if (csiLetter) { result.key = this._buildCsiLetterSequence(csiLetter, modifiers, eventType, reportEventTypes);