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]

Reply via email to