Conversation
zadjii-msft
left a comment
There was a problem hiding this comment.
Thanks for trying to put together a fix here. However, I don't think this works as the right fix. It unfortunately breaks Ctrl+Bksp in WSL in conhost, by causing conhost to instead emit the VT sequence for Ctrl+Alt+Bksp. Take a look at the following table, composed by running showkey -a in:
gnome-terminal- conhost alone, both with and without the PR applied
- conpty by using
vtpipetermto use conhost as the "terminal", both without and with the PR applied
| Key Chord | gnome-terminal |
conhost (before PR) | conhost (with fix) | conpty (before PR) | conpty (with fix) |
|---|---|---|---|---|---|
| Bksp | DEL (0x7f) |
DEL (0x7f) |
DEL (0x7f) |
DEL (0x7f) |
DEL (0x7f) |
| Ctrl+Bksp | ^H (0x08) |
^H (0x08) |
^[^H (0x1b08) |
^H (0x08) |
^[^H (0x1b08) |
| h | h (0x68) |
h (0x68) |
h (0x68) |
h (0x68) |
h (0x68) |
| Ctrl+h | ^H (0x08) |
^H (0x08) |
^H (0x08) |
^H (0x08) |
^[^H (0x1b08) |
| Del | ^[[3~ |
^[[3~ |
^[[3~ |
^[[3~ |
^[[3~ |
| Ctrl+Del | ^[[3;5~ |
^[[3;5~ |
^[[3;5~ |
^[[3;5~ |
^[[3;5~ |
| Alt+Bksp | ^[DEL (0x1b7f) |
^[DEL (0x1b7f) |
^[DEL (0x1b7f) |
||
| Ctrl+Alt+Bksp | ^[^H (0x1b7f) |
^[^H (0x1b7f) |
^[^H (0x1b7f) |
Fundamentally, there's a problem where clients reading VT input can't differentiate between Ctrl+Bksp and Ctrl+h because they have the same encoding (^H), but that's not the problem here, so we won't try and fix it. Win32 client apps can differentiate, but not VT (read:WSL) ones.
I think the problem might be that conpty (InputStateMachineEngine) needs to pick one of those two as what it translates ^H to, and we might have picked wrong. Conpty is treating ^H as Ctrl+h, not Ctrl+Bksp. If we simply change InputStateMachineEngine to process a ^H into a Ctrl+Bksp rather than a Ctrl+h, does that fix the bug instead?
This might have some side effects where Win32 client apps running in a conpty session are unable to read a Ctrl+h keypress (and will treat it like Ctrl+Bksp), but I honestly think that's okay.
Below is some more raw output I gathered from conechokey while investigating.
Details
Conhost only, Before PR, normal (not VT) input:
# bksp
Down: 1 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: \b (0x8) KeyState: 0x20
Down: 0 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: \b (0x8) KeyState: 0x20
# ctrl+bksp
Down: 1 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x28
Down: 1 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: ⌂ (0x7f) KeyState: 0x28
Down: 0 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: ⌂ (0x7f) KeyState: 0x28
Down: 0 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x20
# h
Down: 1 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: h (0x68) KeyState: 0x20
Down: 0 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: h (0x68) KeyState: 0x20
# ctrl+h
Down: 1 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x28
Down: 1 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: \b (0x8) KeyState: 0x28
Down: 0 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: \b (0x8) KeyState: 0x28
Down: 0 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x20
Conpty, Before PR, normal (not VT) input:
# bksp
Down: 1 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: \b (0x8) KeyState: 0x0
Down: 0 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: \b (0x8) KeyState: 0x0
# ctrl+bksp
Down: 1 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x8
Down: 1 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: \b (0x8) KeyState: 0x8
Down: 0 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: \b (0x8) KeyState: 0x8
Down: 0 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x0
# h
Down: 1 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: h (0x68) KeyState: 0x0
Down: 0 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: h (0x68) KeyState: 0x0
# ctrl+h
Down: 1 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x8
Down: 1 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: \b (0x8) KeyState: 0x8
Down: 0 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: \b (0x8) KeyState: 0x8
Down: 0 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x0
Conpty, After PR, normal (not VT) input:
Down: 1 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: \b (0x8) KeyState: 0x0
Down: 0 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: \b (0x8) KeyState: 0x0
Down: 1 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x8
Down: 1 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: ⌂ (0x7f) KeyState: 0x8
Down: 0 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: ⌂ (0x7f) KeyState: 0x8
Down: 0 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x0
Down: 1 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: h (0x68) KeyState: 0x0
Down: 0 Repeat: 1 KeyCode: 0x48 ScanCode: 0x23 Char: h (0x68) KeyState: 0x0
Down: 1 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x8
Down: 1 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: ⌂ (0x7f) KeyState: 0x8
Down: 0 Repeat: 1 KeyCode: 0x8 ScanCode: 0xe Char: ⌂ (0x7f) KeyState: 0x8
Down: 0 Repeat: 1 KeyCode: 0x11 ScanCode: 0x1d Char: \0 (0x0) KeyState: 0x0
|
Thanks for the review @zadjii-msft. That's a lot of good info. I'll work on making |
|
With some new changes making |
|
@zadjii-msft WSL standalone doesn't delete whole words when Ctrl+Bksp is pressed, which explains why I was having my problems above. After testing the behavior of |
zadjii-msft
left a comment
There was a problem hiding this comment.
Yea this is much better. Thanks for looking into this!
| break; | ||
| case L'\b': // backspace | ||
| wch = '\x7f'; | ||
| expectedWch = '\x7f'; |
There was a problem hiding this comment.
nit: this should probably have a break too, even though it's the last case
There was a problem hiding this comment.
Makes sense, the latest commit added the break statement
|
@msftbot merge this in 168 hours |
|
Hello @zadjii-msft! Because you've given me some instructions on how to help merge this pull request, I'll be modifying my merge approach. Here's how I understand your requirements for merging this pull request:
If this doesn't seem right to you, you can tell me to cancel these instructions and use the auto-merge policy that has been configured for this repository. Try telling me "forget everything I just told you". |
carlos-zamora
left a comment
There was a problem hiding this comment.
This has been one of my biggest pain points. Thank you!
|
@msftbot forget everything I just told you |
|
Hello @zadjii-msft! Because you've told me to reset the custom auto-merge settings, I'll use the configured settings for this repository when I'm merging this pull request. |
|
Glad to have this one merged! Thanks |
|
Thanks for making this @mkitzan , I came here looking for an issue and saw that there's an already merged fix, great! |
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Changes the <kbd>Ctrl+Backspace</kbd> input sequence and how it is processed by `InputStateMachineEngine`. Now <kbd>Ctrl+Backspace</kbd> deletes a whole word at a time (tested on WSL, CMD, and PS). <!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> ## References <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [x] Closes #755 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed -> made minor edits to tests * [ ] Requires documentation to be updated * [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #755 <!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments Changed the input sequence for <kbd>Ctrl+Backspace</kbd> to `\x1b\x8` so the sequence would pass through `_DoControlCharacter`. Changed `_DoControlCharacter` to process `\b` in a way which forms the correct `INPUT_RECORD`s to delete whole words. <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed <kbd>Ctrl+Backspace</kbd> works 🎉 (cherry picked from commit 2b79bd0)
|
🎉 Handy links: |
|
Is it possible to get this working with git bash? |
|
You’ll want to look into |
|
I'm on 0.10.781.0 but ctrl-backspace simply seems to send ctrl-h in WSL2 :( |
|
🎉 Once again, thanks for the contribution! This pull request was included in a set of conhost changes that was just |
The reverse VT input path in _DoControlCharacter mapped the byte 0x08 to a Ctrl+Backspace key event (VK_BACK with LEFT_CTRL_PRESSED and character 0x7F). This was introduced in PR microsoft#3935 (Jan 2020) to make Ctrl+Backspace delete whole words, which fixed issue microsoft#755. At that time, the forward path (TerminalInput) also sent 0x08 for Ctrl+Backspace, so the mapping was internally consistent, if a bit unusual. In September 2022, PR microsoft#13894 rewrote the forward path to properly implement DECBKM (Backarrow Key Mode). Under the default DECBKM setting, TerminalInput now sends 0x08 for plain Backspace and 0x7F for Ctrl+Backspace. The reverse path was never updated to match, breaking the roundtrip: a Backspace keypress encodes to 0x08, which decodes back as Ctrl+Backspace. The Cygwin project is working on using OpenConsole.exe for its ConPTY support, and a proposed patch series contains an ugly workaround for this bug that bypasses the normal input pipe and injects raw WriteConsoleInput events whenever 0x08 appears in the stream (https://inbox.sourceware.org/cygwin-patches/20260312113923.1528-4-takashi.yano@nifty.ne.jp/). Fixing the bug here avoids the need to accept that workaround. The fix assigns VK_BACK directly and clears writeCtrl, following the same pattern as the existing L'\x1b' case. This avoids the VkKeyScanW roundtrip through _GenerateKeyFromChar, which was the root cause: it reverse-mapped 0x7F to Ctrl+Backspace (since that is the keyboard combination that produces 0x7F), and then writeCtrl added LEFT_CTRL_PRESSED back after modifierState was zeroed. Note that the 0x7F handler (line 221) has the inverse problem: it maps 0x7F to plain Backspace, but the forward path now uses 0x7F for Ctrl+Backspace. That is a separate issue left for a follow-up. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
In Windows 11, pseudo console has a weird behaviour that the Ctrl-H is translated into Ctrl-Backspace (not Backspace). The analysis in https://inbox.sourceware.org/cygwin-patches/2f8628d2-b79a-95a6-480d-7508375958d5@gmx.de/ shows that the reverse VT input path in conhost's `_DoControlCharacter()` maps the byte 0x08 to a Ctrl+Backspace key event (VK_BACK with LEFT_CTRL_PRESSED and character 0x7F). This was introduced in PR microsoft/terminal#3935 (Jan 2020) to make Ctrl+Backspace delete whole words. In September 2022, PR microsoft/terminal#13894 rewrote the forward path to properly implement DECBKM (Backarrow Key Mode), but the reverse path was never updated to match, breaking the roundtrip. Similary, Backspace (0x7f) is translated into Ctrl-H. Due to this behaviour, `inrec_eq()` in `cons_master_thread()` fails to compare backspace/Ctrl-H events in the input record sequence. This patch is a workaround for the issue that replaces Ctrl-H with backspace (0x7f), which will be translated into Ctrl-H in pseudo console. Signed-off-by: Takashi Yano <takashi.yano@nifty.ne.jp> Reviewed-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Summary of the Pull Request
Changes the Ctrl+Backspace input sequence and how it is processed by
InputStateMachineEngine. Now Ctrl+Backspace deletes a whole word at a time on CMD and PS.References
PR Checklist
Detailed Description of the Pull Request / Additional comments
Changed the input sequence for Ctrl+Backspace to
\x1b\x8so the sequence would pass through_DoControlCharacter. Changed_DoControlCharacterto process\bin a way which forms the correctINPUT_RECORDs to delete whole words.Validation Steps Performed
Ctrl+Backspace works 🎉