self.maxx = maxx - 1
def _decode(self, ch):
- # Decode an integer keystroke or byte to text with the window's encoding.
- # A_CHARTEXT drops any attribute bits.
- return bytes([ch & curses.A_CHARTEXT]).decode(self.win.encoding, 'replace')
+ # An integer keystroke is a byte: get_wch() passes a character as a
+ # string, and getch() reports one as its bytes in the encoding.
+ return bytes([ch & 0xff]).decode(self.win.encoding, 'replace')
def _printable_key(self, ch):
- # Whether the integer keystroke is a printable character, not a key code:
- # 0..255 are character bytes, larger values are function keys.
- return ch <= 0xff and self._decode(ch).isprintable()
+ # Whether the integer keystroke is a printable character rather than a
+ # key code. Key codes occupy [curses.KEY_MIN, curses.KEY_MAX], which
+ # may start above a byte.
+ return (ch <= 0xff
+ and not curses.KEY_MIN <= ch <= curses.KEY_MAX
+ and self._decode(ch).isprintable())
def _end_of_line(self, y):
"""Go to the location of the first blank on the given line,
if y >= self.maxy and x >= self.maxx:
# Use insch() in the lower-right cell; addch() there would push
# the cursor out of the window (an error, and it scrolls a
- # scrollable window). insch() does not decode an int byte
- # through the locale on a wide build, so pass it as text.
+ # scrollable window).
if isinstance(ch, int):
self.win.insch(self._decode(ch), ch & curses.A_ATTRIBUTES)
else:
self.win.insch(ch)
break
- self.win.addch(ch)
+ # Pass the character as text: an integer is a byte, which addch()
+ # takes as a code point on a wide build.
+ if isinstance(ch, int):
+ self.win.addch(self._decode(ch), ch & curses.A_ATTRIBUTES)
+ else:
+ self.win.addch(ch)
# In insert mode keep shifting cells right until a blank one.
if not self.insert_mode or not str(oldch).isprintable():
break
return wrapped
return deco
-def _wide_build():
- # True on a build that stores wide-character cells (built against ncursesw).
- # A wide build accepts a spacing character plus a combining mark in a single
- # cell; a narrow build accepts only one character per cell. This stays a
- # reliable wide/narrow signal even as the wide-character functions (get_wch()
- # and friends) become available on narrow builds too, because the
- # multi-codepoint cell capacity itself is build-specific.
- if not hasattr(curses, 'complexchar'):
- return hasattr(curses.window, 'get_wch')
- try:
- curses.complexchar('e\u0301') # 'e' + combining acute: two code points
- except ValueError:
- return False
- return True
-
-WIDE_BUILD = _wide_build()
+WIDE_BUILD = import_module('_curses')._wide_character_support
def requires_wide_build(test):
@functools.wraps(test)
return True
return len(s.encode(self.stdscr.encoding)) == len(s)
+ def _char_code(self, ch):
+ # The integer the int-input API (addch(int), do_command()) uses for a
+ # character, or None if it has none: a cell holds a single locale byte.
+ try:
+ b = ch.encode(self.stdscr.encoding)
+ except UnicodeEncodeError:
+ return None
+ return b[0] if len(b) == 1 else None
+
def _read_char(self, y, x):
# The character written to a cell, read back for output checks. inch()
# is unusable here: on a wide build it returns the low 8 bits of the
stdscr.addch(2, 3, 'A', curses.A_BOLD)
self.assertIs(stdscr.is_wintouched(), True)
- # The same characters supplied as an int chtype (a byte > 127). The
- # cell is read back with _read_char(), not inch(): on a wide build the
- # int is stored through the locale as a wide character that inch()
- # cannot represent for a character outside Latin-1.
+ # The same characters supplied as an int chtype. The cell is read back
+ # with _read_char(), not inch(): on a wide build the int is stored as a
+ # wide character that inch() cannot represent for a character outside
+ # Latin-1. The int is decoded as a locale byte, so only a single-byte
+ # character round-trips.
for c in ('é', '¤', '€', 'є'):
- try:
- b = c.encode(encoding)
- except UnicodeEncodeError:
- continue
- if len(b) != 1:
+ v = self._char_code(c)
+ if v is None:
continue
- v = b[0]
with self.subTest(c=c):
stdscr.addch(0, 0, v)
self.assertEqual(self._read_char(0, 0), c)
self.assertEqual(stdscr.instr(0, 2, 4), b'BCD ')
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). Both instr() and inch() return the
- # locale byte for any character that fits the locale's single-byte
- # encoding.
- encoding = stdscr.encoding
+ # instr(y, x, 1) reads a single cell byte, so only a character that the
+ # window encoding maps to one byte is checked. inch() returns the cell
+ # value, which is the locale byte.
for ch in ('A', 'é', '¤', '€', 'є'):
try:
- b = ch.encode(encoding)
+ b = ch.encode(stdscr.encoding)
except UnicodeEncodeError:
continue
if len(b) != 1:
continue
+ v = self._char_code(ch)
with self.subTest(ch=ch):
stdscr.addstr(2, 0, ch)
self.assertEqual(stdscr.instr(2, 0, 1), b)
- self.assertEqual(stdscr.inch(2, 0), b[0])
+ self.assertEqual(stdscr.inch(2, 0), v)
def test_coordinate_errors(self):
# Addressing a cell outside the window raises curses.error.
self.assertEqual(win.getch(), b'm'[0])
self.assertEqual(win.getch(), b'\n'[0])
- # A key value > 127 is delivered unchanged (it is not locale text).
- curses.ungetch(0xE9)
- self.assertEqual(win.getch(), 0xE9)
+ # A non-ASCII character encodable as a single byte in the locale
+ # round-trips as that byte.
+ encoding = self.stdscr.encoding
+ for ch in ('é', '¤', '€', 'є'):
+ try:
+ b = ch.encode(encoding)
+ except UnicodeEncodeError:
+ continue
+ if len(b) != 1:
+ continue
+ with self.subTest(ch=ch):
+ curses.ungetch(self._char_code(ch))
+ self.assertEqual(win.getch(), b[0])
+
+ # A key code is delivered unchanged.
+ curses.ungetch(curses.KEY_LEFT)
+ self.assertEqual(win.getch(), curses.KEY_LEFT)
def test_getstr(self):
win = curses.newwin(5, 12, 5, 2)
self.assertEqual(win.inch(0, 0), b'L'[0] | curses.A_REVERSE)
self.assertEqual(win.inch(0, 5), b'#'[0] | curses.A_REVERSE)
- # A non-ASCII background character of an 8-bit locale reads back as its
- # encoded byte. See _encodable for the character set.
+ # A non-ASCII background character reads back as its cell value, the
+ # locale byte.
win.bkgd(' ')
- encoding = win.encoding
for ch in ('é', '¤', '€', 'є'):
- try:
- b = ch.encode(encoding)
- except UnicodeEncodeError:
- continue
- if len(b) != 1:
+ v = self._char_code(ch)
+ if v is None:
continue
with self.subTest(ch=ch):
win.bkgd(ch)
- self.assertEqual(win.getbkgd(), b[0])
+ self.assertEqual(win.getbkgd(), v)
if ord(ch) < 0x100:
# The same byte given as an int. A wide build stores it
# through the locale, so only a Latin-1 byte round-trips.
win.bkgd(' ')
- win.bkgdset(b[0])
- self.assertEqual(win.getbkgd(), b[0])
- win.bkgd(b[0])
- self.assertEqual(win.getbkgd(), b[0])
+ win.bkgdset(v)
+ self.assertEqual(win.getbkgd(), v)
+ win.bkgd(v)
+ self.assertEqual(win.getbkgd(), v)
def test_overlay(self):
srcwin = curses.newwin(5, 18, 3, 4)
self.assertEqual(win.inch(2, 1), b';'[0] | curses.A_STANDOUT)
self.assertEqual(win.inch(3, 1), b'a'[0])
- # A border or line character of an 8-bit locale round-trips as its
- # encoded byte. See _encodable for the character set.
- encoding = win.encoding
+ # A border or line character that fits a single cell byte reads back
+ # via instr() as that byte and via inch() as the cell value.
for ch in ('é', '¤', '€', 'є'):
try:
- b = ch.encode(encoding)
+ b = ch.encode(win.encoding)
except UnicodeEncodeError:
continue
if len(b) != 1:
continue
+ v = self._char_code(ch)
with self.subTest(ch=ch):
win.erase()
win.hline(2, 0, ch, 5)
self.assertEqual(win.instr(2, 0, 5), b * 5)
+ self.assertEqual(win.inch(2, 0) & curses.A_CHARTEXT, v)
win.vline(0, 0, ch, 3)
self.assertEqual(win.instr(0, 0, 1), b)
self.assertEqual(win.instr(1, 0, 1), b)
if ord(ch) < 0x100:
# The same byte given as an int. A wide build stores it
# through the locale, so only a Latin-1 byte round-trips.
- v = b[0]
win.erase()
win.hline(2, 0, v, 5)
self.assertEqual(win.instr(2, 0, 5), b * 5)
def test_insert(self):
"""Test inserting a printable character."""
self.mock_win.reset_mock()
- self.textbox.do_command(ord('a'))
- self.mock_win.addch.assert_called_with(ord('a'))
- self.textbox.do_command(ord('b'))
- self.mock_win.addch.assert_called_with(ord('b'))
- self.textbox.do_command(ord('c'))
- self.mock_win.addch.assert_called_with(ord('c'))
+ # An integer keystroke is decoded to text: addch() would take it as a
+ # code point on a wide build.
+ for ch in 'abc':
+ self.textbox.do_command(ord(ch))
+ self.mock_win.addch.assert_called_with(ch, 0)
self.mock_win.reset_mock()
def test_delete(self):
static int
PyCurses_ConvertToChtype(PyCursesWindowObject *win, PyObject *obj, chtype *ch)
{
- long value;
+ unsigned long long value;
if (PyBytes_Check(obj)) {
if (PyBytes_GET_SIZE(obj) != 1) {
PyErr_Format(PyExc_TypeError,
bytes = PyUnicode_AsEncodedString(obj, encoding, NULL);
if (bytes == NULL)
return 0;
- if (PyBytes_GET_SIZE(bytes) == 1)
- value = (unsigned char)PyBytes_AS_STRING(bytes)[0];
- else
- value = -1;
- Py_DECREF(bytes);
- if (value < 0)
+ if (PyBytes_GET_SIZE(bytes) != 1) {
+ Py_DECREF(bytes);
goto overflow;
+ }
+ value = (unsigned char)PyBytes_AS_STRING(bytes)[0];
+ Py_DECREF(bytes);
}
}
else if (PyLong_CheckExact(obj)) {
- int long_overflow;
- value = PyLong_AsLongAndOverflow(obj, &long_overflow);
- if (long_overflow)
- goto overflow;
+ value = PyLong_AsUnsignedLongLong(obj);
+ if (value == (unsigned long long)-1 && PyErr_Occurred())
+ return 0;
}
else {
PyErr_Format(PyExc_TypeError,
return 0;
}
*ch = (chtype)value;
- if ((long)*ch != value)
+ if ((unsigned long long)*ch != value)
goto overflow;
return 1;
#endif
)
{
- long value;
+ unsigned long long value;
if (PyUnicode_Check(obj)) {
#ifdef HAVE_NCURSESW
value = (unsigned char)PyBytes_AsString(obj)[0];
}
else if (PyLong_CheckExact(obj)) {
- int overflow;
- value = PyLong_AsLongAndOverflow(obj, &overflow);
- if (overflow) {
- PyErr_SetString(PyExc_OverflowError,
- "int doesn't fit in long");
+ value = PyLong_AsUnsignedLongLong(obj);
+ if (value == (unsigned long long)-1 && PyErr_Occurred()) {
return 0;
}
}
}
*ch = (chtype)value;
- if ((long)*ch != value) {
+ if ((unsigned long long)*ch != value) {
PyErr_Format(PyExc_OverflowError,
"byte doesn't fit in chtype");
return 0;
{
/* attr_t is unsigned and at least as wide as chtype, so an attribute
value must be a non-negative integer that fits in attr_t. */
- unsigned long attr = PyLong_AsUnsignedLong(arg);
- if (attr == (unsigned long)-1 && PyErr_Occurred()) {
+ unsigned long long attr = PyLong_AsUnsignedLongLong(arg);
+ if (attr == (unsigned long long)-1 && PyErr_Occurred()) {
return 0;
}
- if (attr > (unsigned long)(attr_t)-1) {
+ if (attr > (unsigned long long)(attr_t)-1) {
PyErr_Format(PyExc_OverflowError,
- "attribute value is greater than maximum (%lu)",
- (unsigned long)(attr_t)-1);
+ "attribute value is greater than maximum (%llu)",
+ (unsigned long long)(attr_t)-1);
return 0;
}
*(attr_t *)ptr = (attr_t)attr;
&_PyCursesComplexCharObject_CAST(self)->cval, &attrs, &pair) < 0) {
return NULL;
}
- return PyLong_FromUnsignedLong((unsigned long)attrs);
+ return PyLong_FromUnsignedLongLong((unsigned long long)attrs);
}
static PyObject *
_curses_window_getattrs_impl(PyCursesWindowObject *self)
/*[clinic end generated code: output=835f499205204ec4 input=bf56a0af5b730bd1]*/
{
- return PyLong_FromUnsignedLong((unsigned long)(attr_t)getattrs(self->win));
+ return PyLong_FromUnsignedLongLong((unsigned long long)(attr_t)getattrs(self->win));
}
/*[clinic input]
curses_window_set_error(self, "getbkgd", NULL);
return NULL;
}
- return PyLong_FromLong(rtn);
+ return PyLong_FromUnsignedLongLong(rtn);
}
/*[clinic input]
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. */
+ /* ncursesw's winch() returns the whole code point, which overflows the
+ chtype's 8-bit character field into the color and attribute bits. */
cchar_t cell = {0};
int rc;
if (!group_right_1) {
return NULL;
}
#endif
- return PyLong_FromUnsignedLong(rtn);
+ return PyLong_FromUnsignedLongLong(rtn);
}
PyDoc_STRVAR(_curses_window_instr__doc__,
pair_number, (int)PAIR_NUMBER(A_COLOR));
return NULL;
}
- return PyLong_FromLong(attr);
+ return PyLong_FromUnsignedLongLong(attr);
}
/*[clinic input]
}
/* This was moved from initcurses() because it core dumped on SGI,
where they're not defined until you've called initscr() */
+ /* Use long long, not long: a chtype constant (the A_* attributes, ACS_*
+ and key codes) can set bits beyond a 32-bit long, which is what long is
+ on LLP64 platforms such as Windows -- A_DIM (0x80000000) would otherwise
+ be sign-extended to a negative number. long long is at least 64 bits
+ everywhere and still represents the negative ERR (-1). */
#define SetDictInt(NAME, VALUE) \
do { \
- PyObject *value = PyLong_FromLong((long)(VALUE)); \
+ PyObject *value = PyLong_FromUnsignedLongLong((unsigned long long)(VALUE)); \
if (value == NULL) { \
return -1; \
} \
@permit_long_summary
_curses.pair_number
- attr: int
+ attr: attr
/
Return the number of the color-pair set by the specified attribute value.
[clinic start generated code]*/
static PyObject *
-_curses_pair_number_impl(PyObject *module, int attr)
-/*[clinic end generated code: output=85bce7d65c0aa3f4 input=b11152a78c2f9abf]*/
+_curses_pair_number_impl(PyObject *module, attr_t attr)
+/*[clinic end generated code: output=04cafc9083329197 input=562273b1a12a06d2]*/
{
PyCursesStatefulInitialised(module);
PyCursesStatefulInitialisedColor(module);
}
#define DICT_ADD_INT_VALUE(NAME, VALUE) \
do { \
- PyObject *value = PyLong_FromLong((long)(VALUE)); \
+ PyObject *value = PyLong_FromUnsignedLongLong((unsigned long long)(VALUE)); \
if (value == NULL) { \
return NULL; \
} \
{
PyCursesStatefulInitialised(module);
- return PyLong_FromUnsignedLong(term_attrs());
+ return PyLong_FromUnsignedLongLong(term_attrs());
}
#endif /* HAVE_CURSES_TERM_ATTRS */
/*[clinic input]
_curses.slk_attron
- attr: long
+ attr: attr
/
Add the given chtype attributes to the soft labels.
[clinic start generated code]*/
static PyObject *
-_curses_slk_attron_impl(PyObject *module, long attr)
-/*[clinic end generated code: output=01aa29848a58ab50 input=fa198a604e3eec04]*/
+_curses_slk_attron_impl(PyObject *module, attr_t attr)
+/*[clinic end generated code: output=c2a4bfac8ddbbf20 input=c2cfffeb7ce6a86e]*/
{
PyCursesStatefulInitialised(module);
return curses_check_err(module, slk_attron((chtype)attr),
/*[clinic input]
_curses.slk_attroff
- attr: long
+ attr: attr
/
Remove the given chtype attributes from the soft labels.
[clinic start generated code]*/
static PyObject *
-_curses_slk_attroff_impl(PyObject *module, long attr)
-/*[clinic end generated code: output=7b172cc37a17811f input=21dab55d43d30b8f]*/
+_curses_slk_attroff_impl(PyObject *module, attr_t attr)
+/*[clinic end generated code: output=321a3b4dc61119bb input=2593027e03cbd302]*/
{
PyCursesStatefulInitialised(module);
return curses_check_err(module, slk_attroff((chtype)attr),
/*[clinic input]
_curses.slk_attrset
- attr: long
+ attr: attr
/
Set the chtype attributes of the soft labels.
[clinic start generated code]*/
static PyObject *
-_curses_slk_attrset_impl(PyObject *module, long attr)
-/*[clinic end generated code: output=1139e2b0f757edfd input=d5c798956a5f046a]*/
+_curses_slk_attrset_impl(PyObject *module, attr_t attr)
+/*[clinic end generated code: output=f396b745cb23fc01 input=5faafda445b2b158]*/
{
PyCursesStatefulInitialised(module);
return curses_check_err(module, slk_attrset((chtype)attr),
/*[clinic end generated code: output=6d47752f82bdc29f input=be38805fdec52149]*/
{
PyCursesStatefulInitialised(module);
- return PyLong_FromUnsignedLong((unsigned long)slk_attr());
+ return PyLong_FromUnsignedLongLong((unsigned long long)slk_attr());
}
#endif
return -1;
}
+ /* Whether a cell holds a character or a single byte of the locale
+ encoding, decided when the module is built. */
+#ifdef HAVE_NCURSESW
+ rc = PyDict_SetItemString(module_dict, "_wide_character_support", Py_True);
+#else
+ rc = PyDict_SetItemString(module_dict, "_wide_character_support", Py_False);
+#endif
+ if (rc < 0) {
+ return -1;
+ }
+
/* Make the version available */
PyObject *curses_version = PyBytes_FromString(PyCursesVersion);
if (curses_version == NULL) {
#define SetDictInt(NAME, VALUE) \
do { \
- PyObject *value = PyLong_FromLong((long)(VALUE)); \
+ PyObject *value = PyLong_FromUnsignedLongLong((unsigned long long)(VALUE)); \
if (value == NULL) { \
return -1; \
} \
{"pair_number", (PyCFunction)_curses_pair_number, METH_O, _curses_pair_number__doc__},
static PyObject *
-_curses_pair_number_impl(PyObject *module, int attr);
+_curses_pair_number_impl(PyObject *module, attr_t attr);
static PyObject *
_curses_pair_number(PyObject *module, PyObject *arg)
{
PyObject *return_value = NULL;
- int attr;
+ attr_t attr;
- attr = PyLong_AsInt(arg);
- if (attr == -1 && PyErr_Occurred()) {
+ if (!attr_converter(arg, &attr)) {
goto exit;
}
return_value = _curses_pair_number_impl(module, attr);
{"slk_attron", (PyCFunction)_curses_slk_attron, METH_O, _curses_slk_attron__doc__},
static PyObject *
-_curses_slk_attron_impl(PyObject *module, long attr);
+_curses_slk_attron_impl(PyObject *module, attr_t attr);
static PyObject *
_curses_slk_attron(PyObject *module, PyObject *arg)
{
PyObject *return_value = NULL;
- long attr;
+ attr_t attr;
- attr = PyLong_AsLong(arg);
- if (attr == -1 && PyErr_Occurred()) {
+ if (!attr_converter(arg, &attr)) {
goto exit;
}
return_value = _curses_slk_attron_impl(module, attr);
{"slk_attroff", (PyCFunction)_curses_slk_attroff, METH_O, _curses_slk_attroff__doc__},
static PyObject *
-_curses_slk_attroff_impl(PyObject *module, long attr);
+_curses_slk_attroff_impl(PyObject *module, attr_t attr);
static PyObject *
_curses_slk_attroff(PyObject *module, PyObject *arg)
{
PyObject *return_value = NULL;
- long attr;
+ attr_t attr;
- attr = PyLong_AsLong(arg);
- if (attr == -1 && PyErr_Occurred()) {
+ if (!attr_converter(arg, &attr)) {
goto exit;
}
return_value = _curses_slk_attroff_impl(module, attr);
{"slk_attrset", (PyCFunction)_curses_slk_attrset, METH_O, _curses_slk_attrset__doc__},
static PyObject *
-_curses_slk_attrset_impl(PyObject *module, long attr);
+_curses_slk_attrset_impl(PyObject *module, attr_t attr);
static PyObject *
_curses_slk_attrset(PyObject *module, PyObject *arg)
{
PyObject *return_value = NULL;
- long attr;
+ attr_t attr;
- attr = PyLong_AsLong(arg);
- if (attr == -1 && PyErr_Occurred()) {
+ if (!attr_converter(arg, &attr)) {
goto exit;
}
return_value = _curses_slk_attrset_impl(module, attr);
#ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
#define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
#endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */
-/*[clinic end generated code: output=ae1359964feefd64 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=cb5525c88ae5c440 input=a9049054013a1b77]*/