diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index e15817ce2e7b0f0..a838ea6e123ffce 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -326,13 +326,10 @@ def test_output_character(self): stdscr.move(2, 0) stdscr.echochar(v) self.assertEqual(self._read_char(2, 0), c) - # insch() round-trips a byte only where its code point equals - # the byte value (Latin-1): on a wide build ncurses winsch - # stores a printable byte directly as a code point instead of - # decoding it through the locale. - if ord(c) < 0x100: - stdscr.insch(1, 0, v) - self.assertEqual(self._read_char(1, 0), c) + # insch() decodes the byte through the locale like addch(), so + # it round-trips the same character. + stdscr.insch(1, 0, v) + self.assertEqual(self._read_char(1, 0), c) # The same characters supplied as a str. Unlike the int path above, a # str is stored as a wide-character cell on a wide build, so every diff --git a/Misc/NEWS.d/next/Library/2026-07-17-20-56-32.gh-issue-153864.WmA9By.rst b/Misc/NEWS.d/next/Library/2026-07-17-20-56-32.gh-issue-153864.WmA9By.rst new file mode 100644 index 000000000000000..79857185d7d0c76 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-17-20-56-32.gh-issue-153864.WmA9By.rst @@ -0,0 +1,3 @@ +On a wide :mod:`curses` build, :meth:`curses.window.insch` now inserts a +non-ASCII byte as the character it encodes in the window's encoding, +consistently with :meth:`~curses.window.addch`, instead of its code point. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 51c4854aa5da52d..2b36ed7f96fc783 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -1871,6 +1871,27 @@ _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1, if (!PyCurses_ConvertToChtype(self, ch, &ch_)) return NULL; +#ifdef HAVE_NCURSESW + /* winsch() does not locale-decode a byte above 127 on a wide build, + unlike waddch(), so decode it here and insert it as a wide character. */ + chtype cch = ch_ & A_CHARTEXT; + if (cch > 127) { + wint_t wc = btowc((int)cch); + if (wc != WEOF) { + cchar_t wch; + wchar_t wstr[2] = { (wchar_t)wc, L'\0' }; + attr_t cattr = (attr_t)((ch_ | (attr_t)attr) & ~(chtype)A_CHARTEXT); + setcchar(&wch, wstr, cattr, PAIR_NUMBER(cattr), NULL); + if (!group_left_1) { + rtn = wins_wch(self->win, &wch); + } + else { + rtn = mvwins_wch(self->win, y, x, &wch); + } + return PyCursesCheckERR_ForWin(self, rtn, "insch"); + } + } +#endif if (!group_left_1) { rtn = winsch(self->win, ch_ | (attr_t)attr); }