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]