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]