Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Doc/library/curses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,8 @@ Reading window contents
The bottom 8 bits are the character proper and the upper bits are the attributes;
extract them with the :data:`A_CHARTEXT` and :data:`A_ATTRIBUTES` bit-masks,
and the color pair with :func:`pair_number`.
The character byte is the locale-encoded byte of the cell's character,
consistent with :meth:`instr`.
It cannot represent a cell holding combining characters, a character that does
not fit in a single byte, or a color pair outside the :func:`color_pair`
range; use :meth:`in_wch` for those, which returns it as a :class:`complexchar`.
Expand Down
10 changes: 4 additions & 6 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -958,10 +958,9 @@ def test_read_from_window(self):
self.assertRaises(ValueError, stdscr.instr, -2)
self.assertRaises(ValueError, stdscr.instr, 0, 2, -2)
# A non-ASCII character of an 8-bit locale reads back as its encoded
# byte (see _encodable for the set). instr() returns the locale bytes
# for any single-byte character; inch() packs the text into a chtype, so
# on a wide build it only round-trips a Latin-1 codepoint (byte ==
# codepoint).
# byte (see _encodable for the set). Both instr() and inch() return the
# locale byte for any character that fits the locale's single-byte
# encoding.
encoding = stdscr.encoding
for ch in ('A', 'é', '¤', '€', 'є'):
try:
Expand All @@ -973,8 +972,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)
if ord(ch) < 0x100:
self.assertEqual(stdscr.inch(2, 0) & curses.A_CHARTEXT, b[0])
self.assertEqual(stdscr.inch(2, 0) & curses.A_CHARTEXT, b[0])

def test_coordinate_errors(self):
# Addressing a cell outside the window raises curses.error.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
On a wide :mod:`curses` build, :meth:`curses.window.inch` now returns the
locale-encoded byte of a non-ASCII character, matching
:meth:`~curses.window.instr`, instead of the low byte of its code point.
32 changes: 32 additions & 0 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,30 @@ 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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may report the error to the caller. Something like:

Details
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index 1e874d65bfc..e9d8bbbc5d0 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -779,7 +779,8 @@ curses_getcchar(const cchar_t *wcval, wchar_t *wstr, attr_t *attrs, int *pair)
    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)
+curses_cell_locale_byte(PyCursesWindowObject *self, chtype rtn,
+                        const cchar_t *cell, const char *funcname)
 {
     wchar_t wstr[CCHARW_MAX + 1];
     attr_t attrs;
@@ -787,13 +788,15 @@ curses_cell_locale_byte(chtype rtn, const cchar_t *cell)
     if (curses_getcchar(cell, wstr, &attrs, &pair) == ERR
         || wstr[0] == L'\0' || wstr[1] != L'\0')
     {
-        return rtn;
+        curses_window_set_error(self, "curses_getcchar", funcname);
+        return (chtype)ERR;
     }
     /* 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;
+        assert(rtn != (chtype)ERR);
     }
     return rtn;
 }
@@ -3638,7 +3641,10 @@ _curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
     if ((group_right_1 ? mvwin_wch(self->win, y, x, &cell)
                        : win_wch(self->win, &cell)) != ERR)
     {
-        rtn = curses_cell_locale_byte(rtn, &cell);
+        rtn = curses_cell_locale_byte(self, rtn, &cell, "inch");
+        if (rtn == (chtype)ERR) {
+            return NULL;
+        }
     }
 #endif
     return PyLong_FromUnsignedLong(rtn);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know what is the good solution here. You still get correct attributes and color pair. getbkgd() returns default background (space) if background character cannot be encoded, but there is not good fallback for inch().

}
/* 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
Expand Down Expand Up @@ -3609,6 +3633,14 @@ _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);
}

Expand Down
Loading