Skip to content

ConPTY modifies escape sequences passed to process input #12166

Closed

Description

Windows Terminal version

1.11.3471.0 (not sure Windows Terminal version matters)

Windows build number

10.0.22000.434

Other Software

No response

Steps to reproduce

Thanks for ConPTY, it's great! I'm developing a terminal emulator using ConPTY API to start a shell process and I've come across the following problem: when Ctrl+Left is pressed, ESC [ 5 D bytes are written to shell's input stream. However, shell sees it as ESC [ D. It's reproduced when cmd.exe or powershell.exe is used as a shell, but works correctly with wsl.exe. Is it a known issue?

More detailed steps to reproduce:

  1. Run forked MiniTerm app (https://github.com/segrey/terminal/tree/main/samples/ConPTY/MiniTerm). Hardcoded cmd.exe will be started.
  2. Type something in the command prompt (e.g. "aaaa bbbb") and press Ctrl+Left/Right. The cursor is moved one position instead of jumping over word.
  3. To view how shell sees written characters, comment 26th line of Program.cs and uncomment the next one. You'll need Node.js installed. Restart the app. Now pressing Ctrl+Left prints "\u001b[D" whereas "\u001b[5D" is written to the process input.
  4. If you replace writer.Write("\x001b[5D") with writer.Write("\x001b[1;5D"), Ctrl+Left works correctly, but I'd like to keep the same logic for Unix and Windows, because \x001b[5D works on Unix correctly.

Expected Behavior

The original escape sequence should be passed to shell: ESC [ 5 D

Actual Behavior

Cut escape sequence is passed to shell: ESC [ D

Activity

  1. ghost added
    Needs-TriageIt's a new issue that the core contributor team needs to triage at the next triage meeting
    on Jan 14, 2022
  2. ghost added
    Needs-Tag-FixDoesn't match tag requirements
    on Jan 14, 2022
  3. 237dmitry commented on Jan 15, 2022

    @237dmitry

    I think it depends not on the terminal, but on what is running in it. For example in PSReadline Ctrl+Left:

    PS > Get-PSReadLineKeyHandler | ? Key -eq 'Ctrl+LeftArrow' | fl *
    
    Key         : Ctrl+LeftArrow
    Function    : BackwardWord
    Description : Move the cursor to the beginning of the current or previous word
    Group       : CursorMovement
    

    And `e[5D working:

    PS > 0..5 | % { Write-Host "Not Repaint 12345`e[5D" -n }
    Not Repaint Not Repaint Not Repaint Not Repaint Not Repaint Not Repaint 12345
    

    In cmd ^[ is Ctrl+[:

    C:\> echo aaaaa bbbbb^[[5D^[[91mbbbbb 
    
  4. segrey commented on Jan 15, 2022

    @segrey
    Author

    0..5 | % { Write-Host "Not Repaint 12345`e[5D" -n }

    Nice example, it also prints for me Not Repaint Not Repaint Not Repaint Not Repaint Not Repaint Not Repaint 12345.
    However, here `e[5D is written by shell to its output stream and my problem is that ConPTY cuts escape sequences passed to shell's input. For example, if you run

    0..5 | % { Write-Host "Not Repaint 123456`e[5D" -n }
    

    output would be:

    Not Repaint 1Not Repaint 1Not Repaint 1Not Repaint 1Not Repaint 1Not Repaint 123456
    

    This is because `e[5D is handled as "move cursor 5 characters left", not "jump cursor backward".

    When Ctrl+Left is pressed, an escape sequence is written to shell's input, then shell analyzes it and writes to its output another escape sequence specifying how many characters cursor should be moved to the left. If a cursor is placed at the end of five character word, then both escape sequences can be the same. Seems it's the source of confusion.

  5. 237dmitry commented on Jan 15, 2022

    @237dmitry

    Is this input?

    PS > "`e[5D_2345" | pwsh -c { [console]::CursorLeft = 5; $input }
    _2345
    
    PS > "`e[5D_2345" | pwsh -c { [console]::CursorLeft = 10; $input }
         _2345
  6. segrey commented on Jan 15, 2022

    @segrey
    Author

    237dmitry I guess no, because in this case `e[5D_2345 string is written to shell's output stream, so it's equivalent to

    pwsh -c { [console]::CursorLeft = 10; "`e[5D_2345" }
    

    You can try MiniTerm and replace cmd.exe with node.exe dump-stdin.js (see step 3 from the description). In this case, shell is dumb - it doesn't output any escape sequences, just dumps its own stdin. It will help to understand my issue.

  7. j4james commented on Jan 16, 2022

    @j4james
    Collaborator

    Perhaps I've misunderstood your problem, but it sounds to me like you're confusing input and output sequences. On a Unix system, trying running showkey -a, and then pressing Ctrl+LeftArrow. On nearly all of the terminals I've tested, the sequence generated is ^[[1;5D (where ^[ is ESC). This includes XTerm, Gnome Terminal, Konsole, MLTerm, Alacritty, Kitty, st, Contour, and WezTerm.

    The sequence \x01b[5D is an output sequence for moving the cursor left by 5 characters, but it doesn't mean anything as an input sequence as far as I know. The input sequence for a LeftArrow without any modifiers is \x01b[D, so it's possible some apps might interpret it as that. Also \x01b[1;5D as an output sequence is just going to move the cursor left by 1, since the second parameter will be ignored.

  8. segrey commented on Jan 16, 2022

    @segrey
    Author

    James Holderness (@j4james) Thanks for the reply. Putting aside what the proper escape sequence for Ctrl+Left is, why does writing \x01b[5D string to a shell input result in \x01b[D string read by the shell?

  9. j4james commented on Jan 16, 2022

    @j4james
    Collaborator

    When you're connecting through conpty, it needs to process your input before forwarding it on to the hosted application or shell. Bear in mind that the process on the other end might be a legacy Windows app, expecting to receive WM_KEYDOWN and WM_KEYUP messages with the VK_LEFT keycode rather the a VT escape sequence.

    So what happens when conpty tries to process your \x01b[5D sequence? That looks like a LeftArrow key, but the 5 doesn't represent a valid modifier parameter, so that is presumedly just ignored. So your \x01b[5D is going to end up being passed through the system as an unmodified LeftArrow.

    Eventually it'll get to a point where it needs to generate an appropriate pair of WM_KEYDOWN and WM_KEYUP messages, or a VT sequence, depending on what the host application has requested. The VT sequence for a LeftArrow is just \x01b[D, so that's what your shell ultimately receives.

    I'm not overly familiar with this area of the code, so I might have some of the details wrong, but I think that's the gist of it.

  10. segrey commented on Jan 17, 2022

    @segrey
    Author

    James Holderness (@j4james) Thanks for the details. I've replaced \x01b[5D (that happens to work for some shells) with more correct \x01b[1;5D, so this issue doesn't bother me anymore. However, if possible, it'd be great if ConPTY would be unopinionated and wouldn't change unrecognized escape sequences. Feel free to close this issue if needed.

  11. DHowett commented on Jan 18, 2022

    @DHowett
    Member

    FWIW the version of ConPTY shipped in the OS lags behind the version in Terminal by quite a few months.

    The version that ships with Terminal (which we're hoping to pull out into a nuget package or something) does pass through all VT input unchanged, if the application on the other end is in ENABLE_VIRTUAL_TERMINAL_INPUT mode.

  12. segrey commented on Jan 18, 2022

    @segrey
    Author

    Dustin L. Howett (@DHowett) Awesome, thanks! ENABLE_VIRTUAL_TERMINAL_INPUT is on by default in my case. I guess this can be closed now.

  13. DHowett commented on Jan 18, 2022

    @DHowett
    Member

    Sergey just to close the loop, this landed in #4856, and was fixed in a couple subsequent PRs. :)

    We initially added it so that a compliant terminal emulator could generate mouse input that we would pass through to an application such that that we didn't have to add a bidirectional mouse input translator. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs-Tag-FixDoesn't match tag requirementsNeeds-TriageIt's a new issue that the core contributor team needs to triage at the next triage meeting

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions