Skip to content

Commit fb9a98a

Browse files
[3.13] gh-153864: Fix curses window.insch() for non-ASCII characters on a wide build (GH-153865) (GH-154154)
On a wide build, winsch() does not locale-decode a byte above 127, unlike waddch(), so insch()/mvinsch() stored the wrong character for a non-ASCII byte of an 8-bit locale ('€' 0xA4 under ISO-8859-15 became U+00A4 '¤'). Decode the byte with btowc() and insert it as a wide character, as addch() effectively does. (cherry picked from commit 223cbff) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9e12f3b commit fb9a98a

3 files changed

Lines changed: 28 additions & 7 deletions

File tree

Lib/test/test_curses.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,13 +332,10 @@ def test_output_character(self):
332332
stdscr.move(2, 0)
333333
stdscr.echochar(v)
334334
self.assertEqual(self._read_char(2, 0), c)
335-
# insch() round-trips a byte only where its code point equals
336-
# the byte value (Latin-1): on a wide build ncurses winsch
337-
# stores a printable byte directly as a code point instead of
338-
# decoding it through the locale.
339-
if ord(c) < 0x100:
340-
stdscr.insch(1, 0, v)
341-
self.assertEqual(self._read_char(1, 0), c)
335+
# insch() decodes the byte through the locale like addch(), so
336+
# it round-trips the same character.
337+
stdscr.insch(1, 0, v)
338+
self.assertEqual(self._read_char(1, 0), c)
342339

343340
# The same characters supplied as a str. Unlike the int path above, a
344341
# str is stored as a wide-character cell on a wide build, so every
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
On a wide :mod:`curses` build, :meth:`curses.window.insch` now inserts a
2+
non-ASCII byte as the character it encodes in the window's encoding,
3+
consistently with :meth:`~curses.window.addch`, instead of its code point.

Modules/_cursesmodule.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1704,6 +1704,27 @@ _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1,
17041704
if (!PyCurses_ConvertToChtype(self, ch, &ch_))
17051705
return NULL;
17061706

1707+
#ifdef HAVE_NCURSESW
1708+
/* winsch() does not locale-decode a byte above 127 on a wide build,
1709+
unlike waddch(), so decode it here and insert it as a wide character. */
1710+
chtype cch = ch_ & A_CHARTEXT;
1711+
if (cch > 127) {
1712+
wint_t wc = btowc((int)cch);
1713+
if (wc != WEOF) {
1714+
cchar_t wch;
1715+
wchar_t wstr[2] = { (wchar_t)wc, L'\0' };
1716+
attr_t cattr = (attr_t)((ch_ | (attr_t)attr) & ~(chtype)A_CHARTEXT);
1717+
setcchar(&wch, wstr, cattr, PAIR_NUMBER(cattr), NULL);
1718+
if (!group_left_1) {
1719+
rtn = wins_wch(self->win, &wch);
1720+
}
1721+
else {
1722+
rtn = mvwins_wch(self->win, y, x, &wch);
1723+
}
1724+
return PyCursesCheckERR(rtn, "insch");
1725+
}
1726+
}
1727+
#endif
17071728
if (!group_left_1) {
17081729
rtn = winsch(self->win, ch_ | (attr_t)attr);
17091730
}

0 commit comments

Comments
 (0)