On a wide build (linked against ncursesw), window.inch() and window.mvinch() return a wrong value for a non-ASCII character that is representable in the locale's 8-bit encoding (such as ISO-8859-15 or KOI8-U). The character comes back as the low 8 bits of the Unicode code point instead of the locale-encoded byte.

This is an ncurses inconsistency, reproducible in plain C. With LANG=en_US.iso885915, where '€' is the single byte 0xA4:

#include <curses.h>
#include <locale.h>
#include <stdio.h>

int main(void)
{
    setlocale(LC_ALL, "");
    initscr();
    addch(0xA4);                 /* '€' in ISO-8859-15 */
    chtype c = mvinch(0, 0);
    char buf[80];
    winnstr(stdscr, buf, sizeof(buf) - 1);
    endwin();
    printf("inch:    0x%02lx\n", (unsigned long)(c & A_CHARTEXT));
    printf("winnstr: 0x%02x\n", (unsigned char)buf[0]);
    return 0;
}
Compiled with cc example.c -lncursesw and run under that locale, inch prints 0xac and winnstr prints 0xa4. 0xAC is 0x20AC & 0xFF, the code point of '€' truncated to 8 bits. The cell is stored correctly; only inch() reads it back wrong.

The cause is an inconsistency between two ncurses functions that the module wraps. winnstr() (used by instr()) re-encodes each cell to the locale encoding: in lib_instr.c it calls getcchar() to get the wchar_t and then wcstombs() to encode it. winch() (used by inch()) does not: in lib_winch.c it returns the raw wchar_t code point placed into the chtype, without wcstombs().

The X/Open note that inch()/winch() are "only guaranteed to operate reliably on character sets in which each character fits into a single byte" is about the locale's encoding, not the function. In an 8-bit locale every character fits in one byte, so these functions are in scope and should return the locale byte. The unsupported case is a character that needs more than one byte in the current locale (any non-ASCII character under UTF-8), which cannot fit a chtype.

The fix is to make inch()/mvinch() re-encode the cell on a wide build, mirroring winnstr(), in _curses_window_inch_impl(): read the cell with win_wch()/mvwin_wch(), extract the wchar_t with getcchar(), and wcstombs() it. If it encodes to exactly one byte, return that byte in the A_CHARTEXT field while keeping the attribute bits from winch(). Otherwise (a multibyte encoding or an error) fall back to the current winch() value. The narrow build is unchanged.

Only inch()/mvinch() are affected. inchstr()/inchnstr() are not exposed by the module, and getbkgd() already round-trips. The write side needs no change: ncurses stores each cell as a wchar_t on a wide build, so the asymmetry is purely on readback. UTF-8 locales remain unsupported for inch().


--

Serhiy Storchaka, CPython core developer



Reply via email to