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