diff --git a/src/common/input/Win32InputMode.test.ts b/src/common/input/Win32InputMode.test.ts index 3381a291..e5dc98b4 100644 --- a/src/common/input/Win32InputMode.test.ts +++ b/src/common/input/Win32InputMode.test.ts @@ -38,8 +38,8 @@ describe('Win32InputMode', () => { }); it('letter key release', () => test({ code: 'KeyA', key: 'a', keyCode: 65 }, false, p => assert.strictEqual(p!.kd, 0))); it('digit key', () => test({ code: 'Digit1', key: '1', keyCode: 49 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x31, 49]))); - it('Enter key', () => test({ code: 'Enter', key: 'Enter', keyCode: 13 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x0D, 0]))); - it('Escape key', () => test({ code: 'Escape', key: 'Escape', keyCode: 27 }, true, p => assert.strictEqual(p!.vk, 0x1B))); + it('Enter key', () => test({ code: 'Enter', key: 'Enter', keyCode: 13 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x0D, 13]))); + it('Escape key', () => test({ code: 'Escape', key: 'Escape', keyCode: 27 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x1B, 27]))); it('Space key', () => test({ code: 'Space', key: ' ', keyCode: 32 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x20, 32]))); }); @@ -91,8 +91,8 @@ describe('Win32InputMode', () => { assert.ok(p!.cs & Win32ControlKeyState.ENHANCED_KEY); })); }); - it('Tab', () => test({ code: 'Tab', key: 'Tab', keyCode: 9 }, true, p => assert.strictEqual(p!.vk, 0x09))); - it('Backspace', () => test({ code: 'Backspace', key: 'Backspace', keyCode: 8 }, true, p => assert.strictEqual(p!.vk, 0x08))); + it('Tab', () => test({ code: 'Tab', key: 'Tab', keyCode: 9 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x09, 9]))); + it('Backspace', () => test({ code: 'Backspace', key: 'Backspace', keyCode: 8 }, true, p => assert.deepStrictEqual([p!.vk, p!.uc], [0x08, 8]))); }); describe('numpad keys', () => { @@ -188,6 +188,16 @@ describe('Win32InputMode', () => { assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED); assert.ok(p!.cs & Win32ControlKeyState.LEFT_ALT_PRESSED); })); + it('Ctrl+Enter produces LF (0x0A)', () => test({ code: 'Enter', key: 'Enter', keyCode: 13, ctrlKey: true }, true, p => { + assert.strictEqual(p!.vk, 0x0D); + assert.strictEqual(p!.uc, 0x0A); + assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED); + })); + it('Ctrl+Backspace produces DEL (0x7F)', () => test({ code: 'Backspace', key: 'Backspace', keyCode: 8, ctrlKey: true }, true, p => { + assert.strictEqual(p!.vk, 0x08); + assert.strictEqual(p!.uc, 0x7F); + assert.ok(p!.cs & Win32ControlKeyState.LEFT_CTRL_PRESSED); + })); }); describe('meta key', () => { diff --git a/src/common/input/Win32InputMode.ts b/src/common/input/Win32InputMode.ts index f446e6b5..6818bc9f 100644 --- a/src/common/input/Win32InputMode.ts +++ b/src/common/input/Win32InputMode.ts @@ -156,6 +156,18 @@ const ENHANCED_KEY_CODES = new Set([ 'MetaLeft', 'MetaRight', ]); +/** + * Mapping of special keys (ev.key values) to their Unicode control character codes. + * These keys have multi-character ev.key strings but produce control characters. + * @see https://docs.microsoft.com/en-us/windows/console/key-event-record-str + */ +const KEY_TO_CONTROL_CHAR: { [key: string]: number } = { + 'Enter': 0x0D, // Carriage return + 'Backspace': 0x08, // Backspace + 'Tab': 0x09, // Horizontal tab + 'Escape': 0x1B, // Escape +}; + /** * Get the Win32 virtual key code for a keyboard event. */ @@ -184,6 +196,23 @@ function getScanCode(ev: IKeyboardEvent): number { * Returns 0 for non-character keys. */ function getUnicodeChar(ev: IKeyboardEvent): number { + // Handle special keys that produce control characters + // Ctrl modifies some of these: Ctrl+Enter=LF, Ctrl+Backspace=DEL + if (ev.ctrlKey && !ev.altKey && !ev.metaKey) { + if (ev.key === 'Enter') { + return 0x0A; // Line feed (Ctrl+Enter) + } + if (ev.key === 'Backspace') { + return 0x7F; // DEL (Ctrl+Backspace) + } + } + + // Check for special keys that always produce control characters + const controlChar = KEY_TO_CONTROL_CHAR[ev.key]; + if (controlChar !== undefined) { + return controlChar; + } + // Only single-character keys produce unicode output if (ev.key.length === 1) { const codePoint = ev.key.codePointAt(0) || 0;