https://github.com/python/cpython/commit/110b9cc2c6f656601c0483667286648e1e91ce60
commit: 110b9cc2c6f656601c0483667286648e1e91ce60
branch: 3.15
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-10-09T20:57:25+03:00
summary:
[3.15] gh-154855: Ask non-ncurses curses for one more character (GH-154870)
(GH-156283)
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 88511bc3573e52..80914b21e27383 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 */
/*
@@ -2217,25 +2225,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]
_______________________________________________
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]