[newlib-cygwin] Cygwin: pty: Add workaround for handling of backspace when pcon enabled

Takashi Yano via Cygwin-cvs <[email protected]> Sun, 29 Mar 2026 00:43:50 +0000 (GMT)
Newsgroups gmane.os.cygwin.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=3Dnewlib-cygwin.git;h=3D9ac383f5c21=
39107570e69a051ab06e56414f91d

commit 9ac383f5c2139107570e69a051ab06e56414f91d
Author: Takashi Yano <[email protected]>
Date:   Sun Feb 22 23:48:23 2026 +0900

    Cygwin: pty: Add workaround for handling of backspace when pcon enabled
   =20
    In Windows 11, pseudo console has an undesired key conversion that
    the Ctrl-H is translated into Ctrl-Backspace (not Backspace).
    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 #3935
    (Jan 2020) to make Ctrl+Backspace delete whole words. In September
    2022, PR #13894 rewrote the forward path to properly implement DECBKM
    (Backarrow Key Mode), but the reverse path was never updated to match,
    breaking the roundtrip.
   =20
    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.
   =20
    Signed-off-by: Takashi Yano <[email protected]>
    Reviewed-by: Johannes Schindelin <[email protected]>

Diff:
---
 winsup/cygwin/fhandler/console.cc       | 12 ++++-
 winsup/cygwin/fhandler/pty.cc           | 78 +++++++++++++++++++++++++++++=
----
 winsup/cygwin/local_includes/fhandler.h |  2 +
 3 files changed, 82 insertions(+), 10 deletions(-)

diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/con=
sole.cc
index 2f46bbc6c..2a52ba575 100644
--- a/winsup/cygwin/fhandler/console.cc
+++ b/winsup/cygwin/fhandler/console.cc
@@ -318,6 +318,16 @@ inrec_eq (const INPUT_RECORD *a, const INPUT_RECORD *b=
, DWORD n)
 	     written event. Therefore they are ignored. */
 	  const KEY_EVENT_RECORD *ak =3D &a[i].Event.KeyEvent;
 	  const KEY_EVENT_RECORD *bk =3D &b[i].Event.KeyEvent;
+	  WCHAR c1 =3D ak->uChar.UnicodeChar;
+	  WCHAR c2 =3D bk->uChar.UnicodeChar;
+	  if (inside_pcon)
+	    {
+	      /* Workaround for pseudo console in Windows 11 */
+	      if (c1 =3D=3D 8) /* Ctrl-H */
+		c1 =3D 127; /* Backspace */
+	      if (c2 =3D=3D 8) /* Ctrl-H */
+		c2 =3D 127; /* Backspace */
+	    }
 	  /* On Windows 11, conhost normalizes wRepeatCount from 0 to 1
 	     on readback. Treat them as equivalent for comparison. */
 	  WORD r1 =3D ak->wRepeatCount;
@@ -327,7 +337,7 @@ inrec_eq (const INPUT_RECORD *a, const INPUT_RECORD *b,=
 DWORD n)
 	  if (r2 =3D=3D 0)
 	    r2 =3D 1;
 	  if (ak->bKeyDown !=3D bk->bKeyDown
-	      || ak->uChar.UnicodeChar !=3D bk->uChar.UnicodeChar
+	      || c1 !=3D c2
 	      || r1 !=3D r2)
 	    return false;
 	}
diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc
index fbc6152e5..257630345 100644
--- a/winsup/cygwin/fhandler/pty.cc
+++ b/winsup/cygwin/fhandler/pty.cc
@@ -1950,7 +1950,8 @@ fhandler_pty_master::fhandler_pty_master (int unit, d=
ev_t via)
     master_thread (NULL), from_master_nat (NULL), to_master_nat (NULL),
     from_slave_nat (NULL), to_slave_nat (NULL), echo_r (NULL), echo_w (NUL=
L),
     dwProcessId (0), to_master (NULL), from_master (NULL),
-    master_fwd_thread (NULL)
+    master_fwd_thread (NULL), h_pcon_in_dupped (NULL),
+    nat_pipe_owner_pid_dupped (0)
 {
   dev_referred_via =3D via;
   if (unit >=3D 0)
@@ -2131,6 +2132,10 @@ fhandler_pty_master::close (int flag)
     termios_printf ("error closing from_master %p, %E", from_master);
   from_master =3D NULL;
=20
+  if (h_pcon_in_dupped)
+    ForceCloseHandle (h_pcon_in_dupped);
+  h_pcon_in_dupped =3D NULL;
+
   return 0;
 }
=20
@@ -2241,28 +2246,77 @@ fhandler_pty_master::write (const void *ptr, size_t=
 len)
     { /* Reaches here when non-cygwin app is foreground and pseudo console
 	 is activated. */
       tmp_pathbuf tp;
-      char *buf =3D (char *) ptr;
+      char *buf =3D tp.c_get ();
       size_t nlen =3D len;
       if (get_ttyp ()->term_code_page !=3D CP_UTF8)
 	{
 	  static mbstate_t mbp;
-	  buf =3D tp.c_get ();
 	  nlen =3D NT_MAX_PATH;
 	  convert_mb_str (CP_UTF8, buf, &nlen,
 			  get_ttyp ()->term_code_page, (const char *) ptr, len,
 			  &mbp);
 	}
+      else
+	memcpy (buf, ptr, nlen);
=20
-      for (size_t i =3D 0; i < nlen; i++)
+      if (get_ttyp ()->nat_pipe_owner_pid !=3D nat_pipe_owner_pid_dupped)
+	{
+	  if (!nat_pipe_owner_self (get_ttyp ()->nat_pipe_owner_pid))
+	    {
+	      if (h_pcon_in_dupped)
+		ForceCloseHandle (h_pcon_in_dupped);
+	      h_pcon_in_dupped =3D NULL;
+	      nat_pipe_owner_pid_dupped =3D 0;
+	      HANDLE pcon_owner =3D OpenProcess (PROCESS_DUP_HANDLE, FALSE,
+					       get_ttyp ()->nat_pipe_owner_pid);
+	      if (pcon_owner)
+		{
+		  DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_in,
+				   GetCurrentProcess (), &h_pcon_in_dupped,
+				   0, FALSE, DUPLICATE_SAME_ACCESS);
+		  nat_pipe_owner_pid_dupped =3D get_ttyp ()->nat_pipe_owner_pid;
+		  CloseHandle (pcon_owner);
+		}
+	    }
+	  else
+	    {
+	      h_pcon_in_dupped =3D get_ttyp ()->h_pcon_in;
+	      nat_pipe_owner_pid_dupped =3D get_ttyp ()->nat_pipe_owner_pid;
+	    }
+	}
+
+      /* Retrieve console mode */
+      DWORD cons_mode =3D ENABLE_VIRTUAL_TERMINAL_INPUT;
+      if (h_pcon_in_dupped && memchr (buf, '\010' /* Ctrl-H */, nlen))
+	{
+	  if (!nat_pipe_owner_self (nat_pipe_owner_pid_dupped))
+	    {
+	      DWORD resume_pid =3D
+		attach_console_temporarily (nat_pipe_owner_pid_dupped);
+	      GetConsoleMode (h_pcon_in_dupped, &cons_mode);
+	      resume_from_temporarily_attach (resume_pid);
+	    }
+	  else
+	    GetConsoleMode (h_pcon_in_dupped, &cons_mode);
+	}
+
+      len =3D nlen;
+      for (size_t i =3D 0, j =3D 0; i < len; i++)
 	{
 	  process_sig_state r =3D process_sigs (buf[i], get_ttyp (), this);
-	  if (r =3D=3D done_with_debugger)
+	  if (r !=3D done_with_debugger)
 	    {
-	      for (size_t j =3D i; j < nlen - 1; j++)
-		buf[j] =3D buf[j + 1];
-	      nlen--;
-	      i--;
+	      char c =3D buf[i];
+	      /* Workaround for pseudo console in Windows 11 */
+	      if (!(cons_mode & ENABLE_VIRTUAL_TERMINAL_INPUT))
+		/* Undesired backspace conversion in pseudo console does
+		   not happen if ENABLE_VIRTUAL_TERMINAL_INPUT is set. */
+		if (c =3D=3D '\010') /* Ctrl-H */
+		  c =3D '\177';  /* Backspace */
+	      buf[j++] =3D c;
 	    }
+	  else
+	    nlen--;
 	}
=20
       DWORD n;
@@ -3145,6 +3199,8 @@ fhandler_pty_master::fixup_after_fork (HANDLE parent)
   from_slave_nat =3D arch->from_slave_nat;
   to_slave_nat =3D arch->to_slave_nat;
 #endif
+  h_pcon_in_dupped =3D NULL;
+  nat_pipe_owner_pid_dupped =3D 0;
   report_tty_counts (this, "inherited master", "");
 }
=20
@@ -3998,6 +4054,10 @@ fhandler_pty_slave::transfer_input (tty::xfer_dir di=
r, HANDLE from, tty *ttyp,
 	    if (r[i].EventType =3D=3D KEY_EVENT && r[i].Event.KeyEvent.bKeyDown)
 	      {
 		DWORD ctrl_key_state =3D r[i].Event.KeyEvent.dwControlKeyState;
+		if (r[i].Event.KeyEvent.uChar.AsciiChar =3D=3D '\010' /* Ctrl-H */
+		    && !(ctrl_key_state & ALT_PRESSED))
+		  /* Workaround for pseudo console in Windows 11 */
+		  r[i].Event.KeyEvent.uChar.AsciiChar =3D '\177'; /* Backspace */
 		if (r[i].Event.KeyEvent.uChar.AsciiChar)
 		  {
 		    if ((ctrl_key_state & ALT_PRESSED)
diff --git a/winsup/cygwin/local_includes/fhandler.h b/winsup/cygwin/local_=
includes/fhandler.h
index 16f55b4f7..7ea04a26c 100644
--- a/winsup/cygwin/local_includes/fhandler.h
+++ b/winsup/cygwin/local_includes/fhandler.h
@@ -2564,6 +2564,8 @@ private:
   HANDLE thread_param_copied_event;
   HANDLE helper_goodbye;
   HANDLE helper_h_process;
+  HANDLE h_pcon_in_dupped;
+  DWORD nat_pipe_owner_pid_dupped;
=20
 public:
   HANDLE get_echo_handle () const { return echo_r; }