[PATCH 2/6] Cygwin: pty: Add workaround for handling of backspace when pcon enabled
Johannes Schindelin
Johannes.Schindelin@gmx.de
Tue Mar 17 15:28:28 GMT 2026
Hi Takashi,
On Tue, 17 Mar 2026, Takashi Yano wrote:
> In Windows 11, pseudo console has a weird behaviour that the Ctrl-H
> is translated into Ctrl-Backspace (not Backspace). 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 that issue
> which pushes the Ctrl-H or Backspace as a ConsoleInput event instead
> of sending char code to pseudo console.
As I have pointed out already in
https://inbox.sourceware.org/cygwin-patches/724fc579-2984-9ec6-9c8e-69b334e966bb@gmx.de/
in response to essentially the same patch, the logic introduced in this
patch is simply too brittle to be considered a valid work-around.
In any case, there is no need to describe this vaguely as "a weird
behavior", not when I spent considerable time to find out, as a way to
allow us to say precisely what is going on and stop hand-waving about the
root cause. To save you one click, I'll replicate the actual bug fix from
https://github.com/dscho/terminal/commit/1b3d526428c86b9357275f11149d736f8928d64b
here:
-- snip --
From 1b3d526428c86b9357275f11149d736f8928d64b Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Mon, 16 Mar 2026 08:30:17 +0100
Subject: [PATCH] Fix 0x08 (Ctrl-H) being incorrectly decoded as Ctrl+Backspace
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 #3935 (Jan 2020) to make
Ctrl+Backspace delete whole words, which fixed issue #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 #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>
---
src/terminal/parser/InputStateMachineEngine.cpp | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp
index e0f663f3f..5fdf9ad1a 100644
--- a/src/terminal/parser/InputStateMachineEngine.cpp
+++ b/src/terminal/parser/InputStateMachineEngine.cpp
@@ -177,10 +177,9 @@ bool InputStateMachineEngine::_DoControlCharacter(const wchar_t wch, const bool
switch (wch)
{
case L'\b':
- // Process Ctrl+Bksp to delete whole words
- actualChar = '\x7f';
- success = _GenerateKeyFromChar(actualChar, vkey, modifierState);
- modifierState = 0;
+ vkey = VK_BACK;
+ writeCtrl = false;
+ success = true;
break;
case L'\r':
writeCtrl = false;
-- snap --
Since `conhost.exe` is essentially "an LTS version of OpenConsole.exe" (my
wording, for the official word see
https://github.com/microsoft/terminal/discussions/12115#discussioncomment-1928563
which explains how `conhost.exe` is built from the same source code as
`OpenConsole.exe`), I can understand that you are now trying to use the
same work-around also for `conhost.exe`.
However, the design of opening the process that owns the console just so
that the Cygwin process can reopen the console handle with the same access
for the purpose of sending raw keyboard events interleaved with the
regular `WriteFile()` of the non-Backspace stuff to a _different_ handle
is so prone to fail that I really believe it better not to take this
patch. Its introduced logic assumes quite a bit about synchronization, for
example, expecting the keyboard events to arrive at the right time, after
the previous `WriteFile(to_slave_nat, ...)` managed to pipe regular
characters through to the console. These assumptions might happen to be
correct a lot of the time, but they _will_ be wrong too often, and as a
consequence the Pseudo Consol support would end up buggier than before
with this patch.
Besides, having two separate loops to handle ^H vs BS assumes that ^H and
BS inputs cannot arrive together, during the same
`fhandler_pty_master::write()` call, which I am fairly certain is
incorrect. With the current design, if there is a BS toward the beginning
of the input and ^H toward the end, the `while (bs_pos1)` loop will
happily write out the `0x7f` byte as part of the `WriteFile (to_slave_nat,
...)` call, excluding the ^H characters of course. And _then_ the `while
(bs_pos2)` loop will happily write all of the input buffer except for the
`0x7f` bytes (but crucially, now including the `0x08` bytes corresponding
to the ^H keystrokes) via the `WriteFile (to_slave_nat, ...)` call, i.e.
it will write most of the payload _again_.
This cannot be correct, and I am rather convinced that the idea behind the
patch (interleaving `WriteFile()` calls to the pipe with sending raw
keyboard events via `WriteConsoleInput()` to a _different_ handle) will
_never_ be robust enough.
Ciao,
Johannes
>
> Signed-off-by: Takashi Yano <takashi.yano@nifty.ne.jp>
> Reviewed-by:
> ---
> winsup/cygwin/fhandler/pty.cc | 64 +++++++++++++++++++++++++++++++++++
> 1 file changed, 64 insertions(+)
>
> diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc
> index 371e67103..bde88ab0e 100644
> --- a/winsup/cygwin/fhandler/pty.cc
> +++ b/winsup/cygwin/fhandler/pty.cc
> @@ -2290,9 +2290,73 @@ fhandler_pty_master::write (const void *ptr, size_t len)
> }
> }
>
> + /* In Windows 11, pseudo console has a weird behaviour that
> + Ctrl-H is translated into Ctrl-Backspace (not Backspace).
> + Similary, backspace (0x7f) is translated into Ctrl-H. The
> + following code is a workaround for that issue. */
> + char *bs_pos1 = (char *) memchr (buf, '\010' /* ^H */, nlen);
> + char *bs_pos2 = (char *) memchr (buf, '\177' /* BS */, nlen);
> + HANDLE h_pcon_in = get_ttyp ()->h_pcon_in;
> + DWORD resume_pid = 0;
> + if ((bs_pos1 || bs_pos2)
> + && !nat_pipe_owner_self (get_ttyp ()->nat_pipe_owner_pid))
> + {
> + HANDLE pcon_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE,
> + get_ttyp ()->nat_pipe_owner_pid);
> + DuplicateHandle (pcon_owner, h_pcon_in,
> + GetCurrentProcess (), &h_pcon_in,
> + 0, FALSE, DUPLICATE_SAME_ACCESS);
> + CloseHandle(pcon_owner);
> + resume_pid =
> + attach_console_temporarily (get_ttyp()->nat_pipe_owner_pid);
> + }
> +
> DWORD n;
> + while (bs_pos1)
> + {
> + if (bs_pos1 - buf > 0)
> + WriteFile (to_slave_nat, buf, bs_pos1 - buf, &n, NULL);
> + INPUT_RECORD r;
> + r.EventType = KEY_EVENT;
> + r.Event.KeyEvent.bKeyDown = 1;
> + r.Event.KeyEvent.wRepeatCount = 0;
> + r.Event.KeyEvent.wVirtualKeyCode = 0;
> + r.Event.KeyEvent.wVirtualScanCode = 0;
> + r.Event.KeyEvent.uChar.AsciiChar = '\010'; /* ^H */
> + r.Event.KeyEvent.dwControlKeyState = LEFT_CTRL_PRESSED;
> + WriteConsoleInput(h_pcon_in, &r, 1, &n);
> + r.Event.KeyEvent.bKeyDown = 0;
> + WriteConsoleInput(h_pcon_in, &r, 1, &n);
> + nlen -= bs_pos1 - buf + 1;
> + buf = bs_pos1 + 1;
> + bs_pos1 = (char *) memchr (buf, '\010' /* ^H */, nlen);
> + }
> + while (bs_pos2)
> + {
> + if (bs_pos2 - buf > 0)
> + WriteFile (to_slave_nat, buf, bs_pos2 - buf, &n, NULL);
> + INPUT_RECORD r;
> + r.EventType = KEY_EVENT;
> + r.Event.KeyEvent.bKeyDown = 1;
> + r.Event.KeyEvent.wRepeatCount = 0;
> + r.Event.KeyEvent.wVirtualKeyCode = 0;
> + r.Event.KeyEvent.wVirtualScanCode = 0;
> + r.Event.KeyEvent.uChar.AsciiChar = '\177'; /* BS */
> + r.Event.KeyEvent.dwControlKeyState = 0;
> + WriteConsoleInput(h_pcon_in, &r, 1, &n);
> + r.Event.KeyEvent.bKeyDown = 0;
> + WriteConsoleInput(h_pcon_in, &r, 1, &n);
> + nlen -= bs_pos2 - buf + 1;
> + buf = bs_pos2 + 1;
> + bs_pos2 = (char *) memchr (buf, '\177' /* BS */, nlen);
> + }
> if (nlen)
> WriteFile (to_slave_nat, buf, nlen, &n, NULL);
> +
> + if (resume_pid)
> + resume_from_temporarily_attach (resume_pid);
> + if (h_pcon_in)
> + CloseHandle(h_pcon_in);
> ReleaseMutex (input_mutex);
>
> return orig_len;
> --
> 2.51.0
>
>
More information about the Cygwin-patches
mailing list