ConPTY isn't behaving as expected #17522

Closed
opened 2026-01-31 05:44:57 +00:00 by claunia · 7 comments
Owner

Originally created by @Pixel-Minions on GitHub (May 20, 2022).

Originally assigned to: @DHowett on GitHub.

Windows Terminal version

No response

Windows build number

No response

Other Software

No response

Steps to reproduce

When sending information from Wezterm to ConPTY it is not, behaving as expected. For example, the case for example of the right control key is not correctly interpreted, it is recognized as the same left control key.

more information here: https://github.com/wez/wezterm/issues/2009 and https://github.com/wez/wezterm/issues/1904

Expected Behavior

No response

Actual Behavior

We expect that ConPTY interpreted the information correctly. For example, the key control left and control right is interpreted independently.

Originally created by @Pixel-Minions on GitHub (May 20, 2022). Originally assigned to: @DHowett on GitHub. ### Windows Terminal version _No response_ ### Windows build number _No response_ ### Other Software _No response_ ### Steps to reproduce When sending information from Wezterm to ConPTY it is not, behaving as expected. For example, the case for example of the right control key is not correctly interpreted, it is recognized as the same left control key. more information here: https://github.com/wez/wezterm/issues/2009 and https://github.com/wez/wezterm/issues/1904 ### Expected Behavior _No response_ ### Actual Behavior We expect that ConPTY interpreted the information correctly. For example, the key control left and control right is interpreted independently.
claunia added the Needs-TriageIssue-BugArea-InputProduct-Conpty labels 2026-01-31 05:44:57 +00:00
Author
Owner

@wez commented on GitHub (May 20, 2022):

Important things to note:

  • wezterm is configured to use win32 input mode in this scenario
  • wezterm is passing through the data from the keyboard event
  • Some inputs (particularly when CTRL is used) do not result in the expected events being passed to Far Manager; the CTRL modifier appears to be lost or misinterpreted

https://github.com/wez/wezterm/issues/1509#issuecomment-1007895196 has a relatively succinct example of what wezterm is passing to conpty in one problem scenario.

I'd love to hear if wezterm should be performing some additional processing on the data that it is serializing for win32 input mode!

@wez commented on GitHub (May 20, 2022): Important things to note: * wezterm is configured to use win32 input mode in this scenario * wezterm is passing through the data from the keyboard event * Some inputs (particularly when CTRL is used) do not result in the expected events being passed to Far Manager; the CTRL modifier appears to be lost or misinterpreted https://github.com/wez/wezterm/issues/1509#issuecomment-1007895196 has a relatively succinct example of what wezterm is passing to conpty in one problem scenario. I'd love to hear if wezterm should be performing some additional processing on the data that it is serializing for win32 input mode!
Author
Owner

@DHowett commented on GitHub (May 20, 2022):

HMM. @wez sorry, I didn't mean to leave you on read! Notification mails for me from projects in the microsoft/ org end up in a different place than notification mails addressed to me from any other repos on earth, and I am much worse about addressing work-related reports out of my personal inbox.

Encoded input as "\u{1b}[67;46;99;1;8;1_"

99 looks familiar, and somewhat unexpected. Also, completely normal and expected.

% [char]99
c

Now, WT sends 3 instead of 99. ETX, or ^C...

What I am at a loss for is two things:

  1. Why we do that (and it clearly works fine)
  2. Why we have code that checks for C and not c, but also no code that checks for ETX but ETX works fine.

Honestly, it probably shares an origin with how conhost gets these events: window messages.

Here's what ^C looks like in window messages:

<000256> 000000000004195A P WM_KEYDOWN nVirtKey:'C' cRepeat:1 ScanCode:2E fExtended:0 fAltDown:0 fRepeat:0 fUp:0
<000257> 000000000004195A P WM_CHAR chCharCode:'3' (3) cRepeat:1 ScanCode:2E fExtended:0 fAltDown:0 fRepeat:0 fUp:0

There's that ETX again!

@DHowett commented on GitHub (May 20, 2022): _HMM_. @wez sorry, I didn't mean to leave you on read! Notification mails for me from projects in the `microsoft/` org end up in a different place than notification mails addressed to me from any other repos on earth, and I am much worse about addressing work-related reports out of my personal inbox. > `Encoded input as "\u{1b}[67;46;99;1;8;1_"` 99 looks familiar, and somewhat unexpected. Also, completely normal and expected. ``` % [char]99 c ``` Now, WT sends `3` instead of `99`. ETX, or `^C`... What I am at a loss for is two things: 1. Why we do that (and it clearly works fine) 2. Why we have code that checks for `C` and not `c`, but also no code that checks for ETX but ETX works fine. Honestly, it probably shares an origin with how conhost gets these events: _window messages_. Here's what `^C` looks like in window messages: ``` <000256> 000000000004195A P WM_KEYDOWN nVirtKey:'C' cRepeat:1 ScanCode:2E fExtended:0 fAltDown:0 fRepeat:0 fUp:0 <000257> 000000000004195A P WM_CHAR chCharCode:'3' (3) cRepeat:1 ScanCode:2E fExtended:0 fAltDown:0 fRepeat:0 fUp:0 ``` There's that ETX again!
Author
Owner

@DHowett commented on GitHub (May 20, 2022):

Ah, so [2] above is a result of me reading the code wrong. It is checking for vkey being C, not uchar being C.

The ^C event win32im seems to be expecting is VK=C UC=ETX MODS=CTRL. I'm comfortable saying that that has its origins in the window messages conhost used to receive.

Realistically, "win32 input mode" is really "what window messages did conhost expect to get?" mode, but... less obvious.

@DHowett commented on GitHub (May 20, 2022): Ah, so [2] above is a result of me reading the code wrong. It is checking for `vkey` being `C`, **not** `uchar` being `C`. The `^C` event win32im seems to be expecting is `VK=C UC=ETX MODS=CTRL`. I'm comfortable saying that that has its origins in the window messages conhost used to receive. Realistically, "win32 input mode" is really "what window messages did conhost expect to get?" mode, but... less obvious.
Author
Owner

@wez commented on GitHub (May 21, 2022):

HMM. @wez sorry, I didn't mean to leave you on read! Notification mails for me from projects in the microsoft/ org end up in a different place than notification mails addressed to me from any other repos on earth, and I am much worse about addressing work-related reports out of my personal inbox.

I figured that it would be something like that; no worries!

@wez commented on GitHub (May 21, 2022): > _HMM_. @wez sorry, I didn't mean to leave you on read! Notification mails for me from projects in the `microsoft/` org end up in a different place than notification mails addressed to me from any other repos on earth, and I am much worse about addressing work-related reports out of my personal inbox. I figured that it would be something like that; no worries!
Author
Owner

@wez commented on GitHub (May 21, 2022):

The ^C event win32im seems to be expecting is VK=C UC=ETX MODS=CTRL. I'm comfortable saying that that has its origins in the window messages conhost used to receive.

Realistically, "win32 input mode" is really "what window messages did conhost expect to get?" mode, but... less obvious.

Heh :-)

So, right now, wezterm populates that third field with the unicode codepoint that corresponds with the keypress. wezterm doesn't use WM_CHAR messages to figure that out.

When CTRL is pressed, should wezterm "normalize" that code point using the normal ctrl mapping logic? eg: uppercase and mask with 0x1f. The actual logic I would prefer to use is based on xterm's behavior which is a superset of that:
1bb31d78ab/termwiz/src/input.rs (L532-L540)

@wez commented on GitHub (May 21, 2022): > The `^C` event win32im seems to be expecting is `VK=C UC=ETX MODS=CTRL`. I'm comfortable saying that that has its origins in the window messages conhost used to receive. > > Realistically, "win32 input mode" is really "what window messages did conhost expect to get?" mode, but... less obvious. Heh :-) So, right now, wezterm populates that third field with the unicode codepoint that corresponds with the keypress. wezterm doesn't use WM_CHAR messages to figure that out. When CTRL is pressed, should wezterm "normalize" that code point using the normal ctrl mapping logic? eg: uppercase and mask with `0x1f`. The actual logic I would prefer to use is based on xterm's behavior which is a superset of that: https://github.com/wez/wezterm/blob/1bb31d78abc0a23559fd6aff51619844bf711859/termwiz/src/input.rs#L532-L540
Author
Owner

@wez commented on GitHub (Jun 5, 2022):

I suspect that we're going to keep finding corner cases; here's the latest one: CTRL-<arrow key> is reported as not working. Here's how wezterm interprets this:

key_event RawKeyEvent { key: Physical(LeftControl), modifiers: CTRL | LEFT_CTRL, phys_code: Some(LeftControl), raw_code: 17, scan_code: 29, repeat_count: 1, key_is_down: true, handled: Handled(false) }
key_event KeyEvent { key: LeftControl, modifiers: CTRL | LEFT_CTRL, repeat_count: 1, key_is_down: true, raw: Some(RawKeyEvent { key: Physical(LeftControl), modifiers: CTRL | LEFT_CTRL, phys_code: Some(LeftControl), raw_code: 17, scan_code: 29, repeat_count: 1, key_is_down: true, handled: Handled(false) }) }
send to pane DOWN key=LeftControl mods=CTRL
Encoded input as "\u{1b}[17;29;0;1;8;1_"

key_event RawKeyEvent { key: Physical(UpArrow), modifiers: CTRL | LEFT_CTRL, phys_code: Some(UpArrow), raw_code: 38, scan_code: 72, repeat_count: 1, key_is_down: true, handled: Handled(false) }
key_event KeyEvent { key: UpArrow, modifiers: CTRL | LEFT_CTRL, repeat_count: 1, key_is_down: true, raw: Some(RawKeyEvent { key: Physical(UpArrow), modifiers: CTRL | LEFT_CTRL, phys_code: Some(UpArrow), raw_code: 38, scan_code: 72, repeat_count: 1, key_is_down: true, handled: Handled(false) }) }
send to pane DOWN key=UpArrow mods=CTRL
Encoded input as "\u{1b}[38;72;0;1;8;1_"

I have two questions about this:

  • What should we be sending instead?
  • How can I conveniently discover what should be sent without having to ask you for help each time? :-)
@wez commented on GitHub (Jun 5, 2022): I suspect that we're going to keep finding corner cases; here's the latest one: `CTRL-<arrow key>` is reported as not working. Here's how wezterm interprets this: ``` key_event RawKeyEvent { key: Physical(LeftControl), modifiers: CTRL | LEFT_CTRL, phys_code: Some(LeftControl), raw_code: 17, scan_code: 29, repeat_count: 1, key_is_down: true, handled: Handled(false) } key_event KeyEvent { key: LeftControl, modifiers: CTRL | LEFT_CTRL, repeat_count: 1, key_is_down: true, raw: Some(RawKeyEvent { key: Physical(LeftControl), modifiers: CTRL | LEFT_CTRL, phys_code: Some(LeftControl), raw_code: 17, scan_code: 29, repeat_count: 1, key_is_down: true, handled: Handled(false) }) } send to pane DOWN key=LeftControl mods=CTRL Encoded input as "\u{1b}[17;29;0;1;8;1_" key_event RawKeyEvent { key: Physical(UpArrow), modifiers: CTRL | LEFT_CTRL, phys_code: Some(UpArrow), raw_code: 38, scan_code: 72, repeat_count: 1, key_is_down: true, handled: Handled(false) } key_event KeyEvent { key: UpArrow, modifiers: CTRL | LEFT_CTRL, repeat_count: 1, key_is_down: true, raw: Some(RawKeyEvent { key: Physical(UpArrow), modifiers: CTRL | LEFT_CTRL, phys_code: Some(UpArrow), raw_code: 38, scan_code: 72, repeat_count: 1, key_is_down: true, handled: Handled(false) }) } send to pane DOWN key=UpArrow mods=CTRL Encoded input as "\u{1b}[38;72;0;1;8;1_" ``` I have two questions about this: * What should we be sending instead? * How can I conveniently discover what should be sent without having to ask you for help each time? :-)
Author
Owner

@DHowett commented on GitHub (Jun 6, 2022):

I suspect that we're going to keep finding corner cases

I'm certain you will! You're the first person to really try to use W32im outside of the limited scope we intended it for. Welcome to the party!¹

The easiest way to figure out what something should generate is to grab a console application that dumps raw input records and see what conhost generates for a given sequence when ENABLE_VIRTUAL_TERMINAL_INPUT is disabled.

echokey suggests this for LCtrl+Up:

Down: 1 Repeat: 1 KeyCode: 0x26 ScanCode: 0x48 Char: \0 (0x0) KeyState: 0x108

That 0x100 in KeyState indicates yet another specific Windowsism: it's better known as ENHANCED_KEY. Any keys that weren't on, like, the ancient 3-key keyboards on IBM-compatibles tend to get those:

Enhanced keys for the IBM® 101- and 102-key keyboards are the INS, DEL, HOME, END, PAGE UP, PAGE DOWN, and direction keys in the clusters to the left of the keypad; and the divide (/) and ENTER keys in the keypad.
source

Incidentally, XAML actually gives it to us as well. That's vaguely horrifying: the "modern" UI framework promotes legacy baggage that follows all Win32 developers around into a full, permanent API affordance:

        if (keyStatus.IsExtendedKey) // this one is theirs
        {
            modifiers |= ControlKeyStates::EnhancedKey; // this one is ours (in Terminal)
        }

¹ Stuff like this is one reason we haven't yet approved it for public consumption. Thanks for being effectively an alpha tester.

@DHowett commented on GitHub (Jun 6, 2022): >I suspect that we're going to keep finding corner cases I'm certain you will! You're the first person to really try to use W32im outside of the limited scope we intended it for. Welcome to the party!¹ The easiest way to figure out what something **should** generate is to grab a [console application that dumps raw input records](https://github.com/microsoft/terminal/blob/main/src/tools/echokey/main.cpp) and see what conhost generates for a given sequence when `ENABLE_VIRTUAL_TERMINAL_INPUT` is _disabled_. echokey suggests this for <kbd>LCtrl+Up</kbd>: ``` Down: 1 Repeat: 1 KeyCode: 0x26 ScanCode: 0x48 Char: \0 (0x0) KeyState: 0x108 ``` That 0x100 in `KeyState` indicates _yet another_ specific Windowsism: it's better known as `ENHANCED_KEY`. Any keys that weren't on, like, the ancient 3-key keyboards on IBM-compatibles tend to get those: > Enhanced keys for the IBM® 101- and 102-key keyboards are the INS, DEL, HOME, END, PAGE UP, PAGE DOWN, and direction keys in the clusters to the left of the keypad; and the divide (/) and ENTER keys in the keypad. > [_source_](https://docs.microsoft.com/en-us/windows/console/key-event-record-str#remarks) Incidentally, _XAML_ actually gives it to us as well. That's vaguely horrifying: the "modern" UI framework promotes legacy baggage that follows all Win32 developers around into a full, permanent API affordance: ```c++ if (keyStatus.IsExtendedKey) // this one is theirs { modifiers |= ControlKeyStates::EnhancedKey; // this one is ours (in Terminal) } ``` ¹ Stuff like this is one reason we haven't yet approved it for public consumption. Thanks for being effectively an alpha tester.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#17522