[PR #16511] Refactor VT terminal input #30937

Open
opened 2026-01-31 09:43:55 +00:00 by claunia · 0 comments
Owner

Original Pull Request: https://github.com/microsoft/terminal/pull/16511

State: closed
Merged: Yes


The primary reason for this refactoring was to simplify the management
of VT input sequences that vary depending on modes, adding support for
the missing application keypad sequences, and preparing the way for
future extensions like S8C1T.

However, it also includes fixes for a number of keyboard related bugs,
including a variety of missing or incorrect mappings for the Ctrl and
Ctrl+Alt key combinations,

References and Relevant Issues

This PR also includes a fix for #10308, which was previously closed as a
duplicate of #10551. I don't think those bugs were related, though, and
although they're both supposed to be fixed in Windows 11, this PR fixes
the issue in Windows 10.

Detailed Description of the Pull Request / Additional comments

The way the input now works, there's a single keyboard map that takes a
virtual key code combined with Ctrl, Alt, and Shift modifier bits
as the lookup key, and the expected VT input sequence as the value. This
map is initially constructed at startup, and then regenerated whenever a
keyboard mode is changed.

This map takes care of the cursor keys, editing keys, function keys, and
keys like BkSp and Return which can be affected by mode changes. The
remaining "graphic" key combinations are determined manually at the time
of input.

The order of precedence looks like this:

  1. If the virtual key is 0 or VK_PACKET, it's considered to be a
    synthesized keyboard event, and the UnicodeChar value is used
    exactly as given.

  2. If it's a numeric keypad key, and Alt is pressed (but not Ctrl),
    then it's assumedly part of an Alt-Numpad composition, so the key
    press is ignored (the generated character will be transmitted when
    the Alt is released).

  3. If the virtual key combined with modifier bits is found in the key
    map described above, then the matched escape sequence will be used
    used as the output.

  4. If a UnicodeChar value has been provided, that will be used as the
    output, but possibly with additional Ctrl and Alt modifiers applied:

    a. If it's an AltGr key, and we've got either two Ctrl keys
    pressed or a left Ctrl key that is distinctly separate from a
    right Alt key, then we will try and convert the character into
    a C0 control code.

    b. If an Alt key is pressed (or in the case of an AltGr value,
    both Alt keys are pressed), then we will convert it into an
    Alt-key sequence by prefixing the character with an ESC.

  5. If we don't have a UnicodeChar, we'll use the ToUnicodeEx API to
    check whether the current keyboard state reflects a dead key, and if
    so, return nothing.

  6. Otherwise we'll make another ToUnicodeEx call but with any Ctrl
    and Alt modifiers removed from the state to determine the base key
    value. Once we have that, we can apply the modifiers ourself.

    a. If the Ctrl key is pressed, we'll try and convert the base value
    into a C0 control code. But if we can't do that, we'll try again
    with the virtual key code (if it's alphanumeric) as a fallback.

    b. If the Alt key is pressed, we'll convert the base value (or
    control code value) into an Alt-key sequence by prefixing it with
    an ESC.

For step 4-a, we determine whether the left Ctrl key is distinctly
separate from the right Alt key by recording the time that those keys
are pressed, and checking for a time gap greater than 50ms. This is
necessary to distinguish between the user pressing Ctrl+AltGr, or
just pressing AltGr alone, which triggers a fake Ctrl key press at
the same time.

Validation Steps Performed

I created a test script to automate key presses in the terminal window
for every relevant key, along with every Ctrl/Alt/Shift modifier, and
every relevant mode combination. I then compared the generated input
sequences with XTerm and a DEC VT240 terminal. The idea wasn't to match
either of them exactly, but to make sure the places where we differed
were intentional and reasonable.

This mostly dealt with the US keyboard layout. Comparing international
layouts wasn't really feasible because DEC, Linux, and Windows keyboard
assignments tend to be quite different. However, I've manually tested a
number of different layouts, and tried to make sure that they were all
working in a reasonable manner.

In terms of unit testing, I haven't done much more than patching the
ones that already existed to get them to pass. They're honestly not
great tests, because they aren't generating events in the form that
you'd expect for a genuine key press, and that can significantly affect
the results, but I can't think of an easy way to improve them.

PR Checklist

**Original Pull Request:** https://github.com/microsoft/terminal/pull/16511 **State:** closed **Merged:** Yes --- The primary reason for this refactoring was to simplify the management of VT input sequences that vary depending on modes, adding support for the missing application keypad sequences, and preparing the way for future extensions like `S8C1T`. However, it also includes fixes for a number of keyboard related bugs, including a variety of missing or incorrect mappings for the `Ctrl` and `Ctrl`+`Alt` key combinations, ## References and Relevant Issues This PR also includes a fix for #10308, which was previously closed as a duplicate of #10551. I don't think those bugs were related, though, and although they're both supposed to be fixed in Windows 11, this PR fixes the issue in Windows 10. ## Detailed Description of the Pull Request / Additional comments The way the input now works, there's a single keyboard map that takes a virtual key code combined with `Ctrl`, `Alt`, and `Shift` modifier bits as the lookup key, and the expected VT input sequence as the value. This map is initially constructed at startup, and then regenerated whenever a keyboard mode is changed. This map takes care of the cursor keys, editing keys, function keys, and keys like `BkSp` and `Return` which can be affected by mode changes. The remaining "graphic" key combinations are determined manually at the time of input. The order of precedence looks like this: 1. If the virtual key is `0` or `VK_PACKET`, it's considered to be a synthesized keyboard event, and the `UnicodeChar` value is used exactly as given. 2. If it's a numeric keypad key, and `Alt` is pressed (but not `Ctrl`), then it's assumedly part of an Alt-Numpad composition, so the key press is ignored (the generated character will be transmitted when the `Alt` is released). 3. If the virtual key combined with modifier bits is found in the key map described above, then the matched escape sequence will be used used as the output. 4. If a `UnicodeChar` value has been provided, that will be used as the output, but possibly with additional Ctrl and Alt modifiers applied: a. If it's an `AltGr` key, and we've got either two `Ctrl` keys pressed or a left `Ctrl` key that is distinctly separate from a right `Alt` key, then we will try and convert the character into a C0 control code. b. If an `Alt` key is pressed (or in the case of an `AltGr` value, both `Alt` keys are pressed), then we will convert it into an Alt-key sequence by prefixing the character with an `ESC`. 5. If we don't have a `UnicodeChar`, we'll use the `ToUnicodeEx` API to check whether the current keyboard state reflects a dead key, and if so, return nothing. 6. Otherwise we'll make another `ToUnicodeEx` call but with any `Ctrl` and `Alt` modifiers removed from the state to determine the base key value. Once we have that, we can apply the modifiers ourself. a. If the `Ctrl` key is pressed, we'll try and convert the base value into a C0 control code. But if we can't do that, we'll try again with the virtual key code (if it's alphanumeric) as a fallback. b. If the `Alt` key is pressed, we'll convert the base value (or control code value) into an Alt-key sequence by prefixing it with an `ESC`. For step 4-a, we determine whether the left `Ctrl` key is distinctly separate from the right `Alt` key by recording the time that those keys are pressed, and checking for a time gap greater than 50ms. This is necessary to distinguish between the user pressing `Ctrl`+`AltGr`, or just pressing `AltGr` alone, which triggers a fake `Ctrl` key press at the same time. ## Validation Steps Performed I created a test script to automate key presses in the terminal window for every relevant key, along with every Ctrl/Alt/Shift modifier, and every relevant mode combination. I then compared the generated input sequences with XTerm and a DEC VT240 terminal. The idea wasn't to match either of them exactly, but to make sure the places where we differed were intentional and reasonable. This mostly dealt with the US keyboard layout. Comparing international layouts wasn't really feasible because DEC, Linux, and Windows keyboard assignments tend to be quite different. However, I've manually tested a number of different layouts, and tried to make sure that they were all working in a reasonable manner. In terms of unit testing, I haven't done much more than patching the ones that already existed to get them to pass. They're honestly not great tests, because they aren't generating events in the form that you'd expect for a genuine key press, and that can significantly affect the results, but I can't think of an easy way to improve them. ## PR Checklist - [x] Closes #16506 - [x] Closes #16508 - [x] Closes #16509 - [x] Closes #16510 - [x] Closes #3483 - [x] Closes #11194 - [x] Closes #11700 - [x] Closes #12555 - [x] Closes #13319 - [x] Closes #15367 - [x] Closes #16173 - [x] Tests added/passed
claunia added the pull-request label 2026-01-31 09:43:55 +00:00
Sign in to join this conversation.
No Label pull-request
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#30937