https://github.com/python/cpython/commit/b6188749d69e9609a83ee0d526013920da314ab2 commit: b6188749d69e9609a83ee0d526013920da314ab2 branch: main author: Serhiy Storchaka <[email protected]> committer: serhiy-storchaka <[email protected]> date: 2026-07-26T11:02:50+03:00 summary:
gh-153862: Fix spurious color pair in curses window.inch() on a wide build (GH-154703) winch() returns the whole code point, so inch() replacing only its low 8 bits left the high bits in the color field. Rebuild from getcchar()'s attributes and color pair. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> files: 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 17837c0219f88e..ad5893e6754f68 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -974,7 +974,7 @@ def test_read_from_window(self): with self.subTest(ch=ch): stdscr.addstr(2, 0, ch) self.assertEqual(stdscr.instr(2, 0, 1), b) - self.assertEqual(stdscr.inch(2, 0) & curses.A_CHARTEXT, b[0]) + self.assertEqual(stdscr.inch(2, 0), b[0]) def test_coordinate_errors(self): # Addressing a cell outside the window raises curses.error. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index f893c2080fc3f3..b2d745332317a3 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -776,30 +776,6 @@ curses_getcchar(const cchar_t *wcval, wchar_t *wstr, attr_t *attrs, int *pair) return rtn; } -/* winch() returns the low 8 bits of the character's code point with no locale - conversion, unlike instr(), so recover the locale byte from the wide cell - when the character maps to exactly one byte, keeping the attribute and color - bits in RTN. A character with no single-byte form is left to winch(). */ -static chtype -curses_cell_locale_byte(chtype rtn, const cchar_t *cell) -{ - wchar_t wstr[CCHARW_MAX + 1]; - attr_t attrs; - int pair; - if (curses_getcchar(cell, wstr, &attrs, &pair) == ERR - || wstr[0] == L'\0' || wstr[1] != L'\0') - { - return rtn; - } - /* wctob() mirrors ncurses' own _nc_to_char(): the single-byte form, or EOF - when the character has none in this locale. */ - int byte = wctob(wstr[0]); - if (byte != EOF) { - rtn = (rtn & ~(chtype)A_CHARTEXT) | (unsigned char)byte; - } - return rtn; -} - /* Hash one cell by value (text, attributes, pair) -- consistent with the equality comparison, not the raw cchar_t whose padding and unused text tail it ignores. Zero the key first so those bytes are deterministic, then @@ -3650,7 +3626,40 @@ _curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1, { chtype rtn; const char *funcname; - +#ifdef HAVE_NCURSESW + /* ncursesw's winch() returns the character's whole code point instead of + its locale byte, overflowing the chtype's 8-bit character field into the + color and attribute bits; read the wide cell and rebuild it instead. */ + cchar_t cell = {0}; + int rc; + if (!group_right_1) { + rc = win_wch(self->win, &cell); + funcname = "win_wch"; + } + else { + rc = mvwin_wch(self->win, y, x, &cell); + funcname = "mvwin_wch"; + } + if (rc == ERR) { + curses_window_set_error(self, funcname, "inch"); + return NULL; + } + wchar_t wstr[CCHARW_MAX + 1]; + attr_t attrs; + int pair; + if (curses_getcchar(&cell, wstr, &attrs, &pair) == ERR) { + curses_window_set_error(self, "getcchar", "inch"); + return NULL; + } + int byte = 0; + if (wstr[0] != L'\0' && wstr[1] == L'\0') { + byte = wctob(wstr[0]); + if (byte == EOF) { + byte = 0; + } + } + rtn = (chtype)byte | (attrs & ~(attr_t)A_COLOR) | COLOR_PAIR(pair); +#else if (!group_right_1) { rtn = winch(self->win); funcname = "winch"; @@ -3663,13 +3672,6 @@ _curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1, curses_window_set_error(self, funcname, "inch"); return NULL; } -#ifdef HAVE_NCURSESW - curses_cell_t cell = {0}; - if ((group_right_1 ? mvwin_wch(self->win, y, x, &cell) - : win_wch(self->win, &cell)) != ERR) - { - rtn = curses_cell_locale_byte(rtn, &cell); - } #endif return PyLong_FromUnsignedLong(rtn); } _______________________________________________ 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]
