Skip to content

Commit 25a680e

Browse files
gh-153862: Rebuild curses window.inch() from the wide cell
On a wide build inch() derived its result from winch(), whose character is the whole code point rather than a locale byte, so a code point past 0xff overflowed into the color field and past 0xffff into the attribute bits. Read the cell with win_wch() and build the chtype from the locale byte plus the attributes and color pair reported by getcchar(). The character is 0 when it has no single-byte form; win_wch() and getcchar() failures now raise. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c19389a commit 25a680e

1 file changed

Lines changed: 34 additions & 32 deletions

File tree

Modules/_cursesmodule.c

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -776,30 +776,6 @@ curses_getcchar(const cchar_t *wcval, wchar_t *wstr, attr_t *attrs, int *pair)
776776
return rtn;
777777
}
778778

779-
/* winch() does not convert the character to its locale byte, and its bits above
780-
the character may be code-point bits rather than attributes, so rebuild the
781-
value from the locale byte plus getcchar()'s attributes and color pair. A
782-
character with no single-byte form is left to winch(). */
783-
static chtype
784-
curses_cell_locale_byte(chtype rtn, const cchar_t *cell)
785-
{
786-
wchar_t wstr[CCHARW_MAX + 1];
787-
attr_t attrs;
788-
int pair;
789-
if (curses_getcchar(cell, wstr, &attrs, &pair) == ERR
790-
|| wstr[0] == L'\0' || wstr[1] != L'\0')
791-
{
792-
return rtn;
793-
}
794-
/* wctob() mirrors ncurses' own _nc_to_char(): the single-byte form, or EOF
795-
when the character has none in this locale. */
796-
int byte = wctob(wstr[0]);
797-
if (byte != EOF) {
798-
rtn = (unsigned char)byte | (attrs & ~(attr_t)A_COLOR) | COLOR_PAIR(pair);
799-
}
800-
return rtn;
801-
}
802-
803779
/* Hash one cell by value (text, attributes, pair) -- consistent with the
804780
equality comparison, not the raw cchar_t whose padding and unused text tail
805781
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,
36503626
{
36513627
chtype rtn;
36523628
const char *funcname;
3653-
3629+
#ifdef HAVE_NCURSESW
3630+
/* ncursesw's winch() returns the character's whole code point instead of
3631+
its locale byte, overflowing the chtype's 8-bit character field into the
3632+
color and attribute bits; read the wide cell and rebuild it instead. */
3633+
cchar_t cell = {0};
3634+
int rc;
3635+
if (!group_right_1) {
3636+
rc = win_wch(self->win, &cell);
3637+
funcname = "win_wch";
3638+
}
3639+
else {
3640+
rc = mvwin_wch(self->win, y, x, &cell);
3641+
funcname = "mvwin_wch";
3642+
}
3643+
if (rc == ERR) {
3644+
curses_window_set_error(self, funcname, "inch");
3645+
return NULL;
3646+
}
3647+
wchar_t wstr[CCHARW_MAX + 1];
3648+
attr_t attrs;
3649+
int pair;
3650+
if (curses_getcchar(&cell, wstr, &attrs, &pair) == ERR) {
3651+
curses_window_set_error(self, "getcchar", "inch");
3652+
return NULL;
3653+
}
3654+
int byte = 0;
3655+
if (wstr[0] != L'\0' && wstr[1] == L'\0') {
3656+
byte = wctob(wstr[0]);
3657+
if (byte == EOF) {
3658+
byte = 0;
3659+
}
3660+
}
3661+
rtn = (chtype)byte | (attrs & ~(attr_t)A_COLOR) | COLOR_PAIR(pair);
3662+
#else
36543663
if (!group_right_1) {
36553664
rtn = winch(self->win);
36563665
funcname = "winch";
@@ -3663,13 +3672,6 @@ _curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
36633672
curses_window_set_error(self, funcname, "inch");
36643673
return NULL;
36653674
}
3666-
#ifdef HAVE_NCURSESW
3667-
curses_cell_t cell = {0};
3668-
if ((group_right_1 ? mvwin_wch(self->win, y, x, &cell)
3669-
: win_wch(self->win, &cell)) != ERR)
3670-
{
3671-
rtn = curses_cell_locale_byte(rtn, &cell);
3672-
}
36733675
#endif
36743676
return PyLong_FromUnsignedLong(rtn);
36753677
}

0 commit comments

Comments
 (0)