https://github.com/python/cpython/commit/46d7aa9fbd2fafb099da6a61f3865620d4dc9125 commit: 46d7aa9fbd2fafb099da6a61f3865620d4dc9125 branch: 3.15 author: Serhiy Storchaka <[email protected]> committer: serhiy-storchaka <[email protected]> date: 2026-07-19T12:45:26Z summary:
[3.15] gh-153864: Fix curses window.insch() for non-ASCII characters on a wide build (GH-153865) (GH-154117) On a wide build, winsch() does not locale-decode a byte above 127, unlike waddch(), so insch() inserted '¤' (U+00A4) instead of '€' for byte 0xA4 under ISO-8859-15. Decode the byte with btowc() and insert it as a wide character, like addch(). (cherry picked from commit 223cbffca6df7e4c8c77b360a4e6ba0472255643) Co-authored-by: Claude Opus 4.8 <[email protected]> files: A Misc/NEWS.d/next/Library/2026-07-17-20-56-32.gh-issue-153864.WmA9By.rst M Lib/test/test_curses.py M Modules/_cursesmodule.c diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 737735fbcc0032..d5cd7483d77ff0 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -329,13 +329,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 dd5af63fcc8a46..3a60749d440248 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -2064,6 +2064,32 @@ _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1, return NULL; const char *funcname; +#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); + if (setcchar(&wch, wstr, cattr, PAIR_NUMBER(cattr), NULL) == ERR) { + curses_window_set_error(self, "setcchar", "insch"); + return NULL; + } + if (!group_left_1) { + rtn = wins_wch(self->win, &wch); + funcname = "wins_wch"; + } + else { + rtn = mvwins_wch(self->win, y, x, &wch); + funcname = "mvwins_wch"; + } + return curses_window_check_err(self, rtn, funcname, "insch"); + } + } +#endif if (!group_left_1) { rtn = winsch(self->win, ch_ | (attr_t)attr); funcname = "winsch"; _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
