[3.13] gh-154855: Ask non-ncurses curses for one more character (GH-154870) (GH-156285)
serhiy-storchaka <[email protected]>
| Newsgroups | gmane.comp.python.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://github.com/python/cpython/commit/7d7fa9d73b9e6e0cd14a34e7953010f80aa290d4 commit: 7d7fa9d73b9e6e0cd14a34e7953010f80aa290d4 branch: 3.13 author: Serhiy Storchaka <[email protected]> committer: serhiy-storchaka <[email protected]> date: 2026-08-23T13:07:20Z summary: [3.13] gh-154855: Ask non-ncurses curses for one more character (GH-154870) (GH-156285) Passing n to the library is ncurses' reading of n: it stores n characters and adds a terminator. NetBSD curses counts the terminator in n. Ask a library that is neither ncurses nor PDCurses for n + 1, and read again if it stored more than asked; truncating could split a multibyte character. This is not possible for input, so getstr() is left as it is. instr() now takes the length from the value returned by winnstr(), as X/Open specifies, instead of searching for a terminator which it does not. (cherry picked from commit 43a1869f7eea006f04647d4225d5fda80cb3fbd9) files: A Misc/NEWS.d/next/Library/2026-07-29-14-20-40.gh-issue-154855.vfF453.rst M Modules/_cursesmodule.c 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 00000000000000..d8895e32bafe8a --- /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 fa3c6cda76d779..e1c26ad7f06bc9 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -185,6 +185,14 @@ static int initialisedcolors = FALSE; while these functions are still in use. */ static char *screen_encoding = NULL; +/* 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 Macros */ #define PyCursesSetupTermCalled \ if (initialised_setupterm != TRUE) { \ @@ -1839,13 +1847,13 @@ bytes are read. static PyObject * PyCursesWindow_InStr(PyCursesWindowObject *self, PyObject *args) { - int x, y, n; + int x = 0, y = 0, n; char rtn[1024]; /* This should be big enough.. I hope */ - int rtn2; + int rtn2, use_xy = 0; switch (PyTuple_Size(args)) { case 0: - rtn2 = winnstr(self->win,rtn, 1023); + n = 1023; break; case 1: if (!PyArg_ParseTuple(args,"i;n", &n)) @@ -1854,12 +1862,13 @@ PyCursesWindow_InStr(PyCursesWindowObject *self, PyObject *args) PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative"); return NULL; } - rtn2 = winnstr(self->win, rtn, Py_MIN(n, 1023)); + n = Py_MIN(n, 1023); break; case 2: if (!PyArg_ParseTuple(args,"ii;y,x",&y,&x)) return NULL; - rtn2 = mvwinnstr(self->win,y,x,rtn,1023); + n = 1023; + use_xy = 1; break; case 3: if (!PyArg_ParseTuple(args, "iii;y,x,n", &y, &x, &n)) @@ -1868,15 +1877,31 @@ PyCursesWindow_InStr(PyCursesWindowObject *self, PyObject *args) PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative"); return NULL; } - rtn2 = mvwinnstr(self->win, y, x, rtn, Py_MIN(n,1023)); + n = Py_MIN(n, 1023); + use_xy = 1; break; default: PyErr_SetString(PyExc_TypeError, "instr requires 0 or 3 arguments"); return NULL; } + + /* Read again if the library stored more than asked: truncating could + split a multibyte character. */ + for (int ask = n + CURSES_STR_EXTRA; ; ask = n) { + if (use_xy) { + rtn2 = mvwinnstr(self->win, y, x, rtn, ask); + } + else { + rtn2 = winnstr(self->win, rtn, ask); + } + if (rtn2 == ERR || rtn2 <= n) { + break; + } + } + if (rtn2 == ERR) - rtn[0] = 0; - return PyBytes_FromString(rtn); + return PyBytes_FromStringAndSize(NULL, 0); + return PyBytes_FromStringAndSize(rtn, rtn2); } /*[clinic input] _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]