https://github.com/python/cpython/commit/f6b1201e20345f3a7a546d458e9f1f2fbdd8fa61
commit: f6b1201e20345f3a7a546d458e9f1f2fbdd8fa61
branch: 3.14
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-23T13:10:59Z
summary:

[3.14] gh-154855: Ask non-ncurses curses for one more character (GH-154870) 
(GH-156284)

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 6008802b81c68a..8d8544b4d9c20f 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -215,6 +215,14 @@ static int curses_start_color_called = FALSE;
    while these functions are still in use. */
 static char *curses_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 Checking Procedures */
 
 /*
@@ -2014,25 +2022,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;
     res = PyBytes_FromStringAndSize(NULL, n + 1);
     if (res == NULL) {
         return NULL;
     }
     char *buf = PyBytes_AS_STRING(res);
 
-    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) {
         Py_DECREF(res);
         return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
     }
-    _PyBytes_Resize(&res, strlen(buf));  // 'res' is set to NULL on failure
+    _PyBytes_Resize(&res, rtn);  // 'res' is set to NULL on failure
     return res;
 }
 

_______________________________________________
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]

Reply via email to