diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index b99e4efbb83ce4..16c2caa958bc38 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -785,13 +785,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 00000000000000..79857185d7d0c7 --- /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 cc68a51f0bed93..382e3de80e6536 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -3544,6 +3544,24 @@ _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1, if (type == 0) { return NULL; } + if (type == 1) { + /* 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. (gh-153864) */ + chtype cch = ch_ & A_CHARTEXT; + if (cch > 127) { + wint_t wc = btowc((int)cch); + if (wc != WEOF) { + wchar_t wstr[2] = { (wchar_t)wc, L'\0' }; + attr_t cattr = (attr_t)((ch_ | attr) & ~(chtype)A_CHARTEXT); + if (curses_setcchar(&wch, wstr, cattr, PAIR_NUMBER(cattr)) == ERR) { + curses_window_set_error(self, "setcchar", "insch"); + return NULL; + } + type = 2; + } + } + } if (type == 2) { if (!group_left_1) { rtn = wins_wch(self->win, &wch);