Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix :meth:`curses.window.instr`, :meth:`~curses.window.in_wstr` and
:meth:`~curses.window.in_wchstr` returning one character too few when the
:mod:`curses` module is built against a curses library that counts the
terminator in the requested length, such as the NetBSD one.
55 changes: 41 additions & 14 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,14 @@ curses_window_set_null_error(PyCursesWindowObject *win,
_curses_set_null_error(state, curses_funcname, python_funcname);
}

/* ncurses and PDCurses store n characters and add a terminator; NetBSD
curses counts the terminator in n. Ask an unknown library for one more. */
#if defined(NCURSES_VERSION) || defined(PDCURSES)
# define CURSES_STR_EXTRA 0
#else
# define CURSES_STR_EXTRA 1
#endif

/* Utility Checking Procedures */

/*
Expand Down Expand Up @@ -3796,25 +3804,33 @@ curses_window_instr_bytes(PyCursesWindowObject *self, PyObject *args,
return NULL;
}

n = Py_MIN(n, max_buf_size - 1);
n = Py_MIN(n, max_buf_size - 1 - CURSES_STR_EXTRA);
n += CURSES_STR_EXTRA;
PyBytesWriter *writer = PyBytesWriter_Create(n + 1);
if (writer == NULL) {
return NULL;
}
char *buf = PyBytesWriter_GetData(writer);

if (use_xy) {
rtn = mvwinnstr(self->win, y, x, buf, n);
}
else {
rtn = winnstr(self->win, buf, n);
/* Read again if the library stored more than asked: truncating could
split a multibyte character. */
for (unsigned int want = n - CURSES_STR_EXTRA; ; n = want) {
if (use_xy) {
rtn = mvwinnstr(self->win, y, x, buf, n);
}
else {
rtn = winnstr(self->win, buf, n);
}
if (rtn == ERR || (unsigned int)rtn <= want) {
break;
}
}

if (rtn == ERR) {
PyBytesWriter_Discard(writer);
return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
}
return PyBytesWriter_FinishWithSize(writer, strlen(buf));
return PyBytesWriter_FinishWithSize(writer, rtn);
}

static PyObject *
Expand Down Expand Up @@ -3939,17 +3955,25 @@ PyCursesWindow_in_wstr(PyObject *op, PyObject *args)
return NULL;
}

n = Py_MIN(n, max_buf_size - 1);
n = Py_MIN(n, max_buf_size - 1 - CURSES_STR_EXTRA);
n += CURSES_STR_EXTRA;
wchar_t *buf = PyMem_New(wchar_t, n + 1);
if (buf == NULL) {
return PyErr_NoMemory();
}

if (use_xy) {
rtn = mvwinnwstr(self->win, y, x, buf, n);
}
else {
rtn = winnwstr(self->win, buf, n);
/* Read again if the library stored more than asked: truncating could
separate a combining character from its base. */
for (unsigned int want = n - CURSES_STR_EXTRA; ; n = want) {
if (use_xy) {
rtn = mvwinnwstr(self->win, y, x, buf, n);
}
else {
rtn = winnwstr(self->win, buf, n);
}
if (rtn == ERR || (unsigned int)rtn <= want) {
break;
}
}

if (rtn == ERR) {
Expand Down Expand Up @@ -4003,7 +4027,8 @@ PyCursesWindow_in_wchstr(PyObject *op, PyObject *args)
return NULL;
}

n = Py_MIN(n, max_buf_size - 1);
n = Py_MIN(n, max_buf_size - 1 - CURSES_STR_EXTRA);
n += CURSES_STR_EXTRA;
cursesmodule_state *state = get_cursesmodule_state_by_win(self);
/* Zero the cells: reading a cell back through getcchar() relies on the
cchar_t text array being NUL-terminated, which some curses libraries
Expand All @@ -4026,6 +4051,7 @@ PyCursesWindow_in_wchstr(PyObject *op, PyObject *args)
return PyCursesComplexStr_New(state, NULL, 0);
}

n -= CURSES_STR_EXTRA;
/* win_wchnstr() stores at most n cells and zero-terminates the array at
the actual count; every real cell holds at least a space, so the first
empty cell marks the end of the run. */
Expand Down Expand Up @@ -4058,6 +4084,7 @@ PyCursesWindow_in_wchstr(PyObject *op, PyObject *args)
return PyCursesComplexStr_New(state, NULL, 0);
}

n -= CURSES_STR_EXTRA;
Py_ssize_t count = 0;
while (count < (Py_ssize_t)n && buf[count] != 0) {
count++;
Expand Down
Loading