From: Serhiy Storchaka Date: Sun, 19 Jul 2026 08:45:06 +0000 (+0300) Subject: gh-153864: Fix curses window.insch() for non-ASCII characters on a wide build (GH... X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;h=223cbffca6df7e4c8c77b360a4e6ba0472255643;p=thirdparty%2FPython%2Fcpython.git gh-153864: Fix curses window.insch() for non-ASCII characters on a wide build (GH-153865) 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(). Co-Authored-By: Claude Opus 4.8 --- diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 2a222ecf5272..b0592d3f1d92 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -785,13 +785,10 @@ class TestCurses(unittest.TestCase): 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 000000000000..79857185d7d0 --- /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 1e874d65bfca..2253d9ff3d70 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -3568,6 +3568,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);