diff --git a/Misc/NEWS.d/next/Library/2026-07-29-14-20-40.gh-issue-154855.vfF453.rst b/Misc/NEWS.d/next/Library/2026-07-29-14-20-40.gh-issue-154855.vfF453.rst new file mode 100644 index 000000000000000..d8895e32bafe8a9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-29-14-20-40.gh-issue-154855.vfF453.rst @@ -0,0 +1,3 @@ +Fix :meth:`curses.window.instr` 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. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 82c8e642a51154f..168bf78711cf5f9 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -310,6 +310,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 */ /* @@ -2210,25 +2218,33 @@ PyCursesWindow_instr(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; 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); } /*[clinic input]