Skip to content

Commit 7d7fa9d

Browse files
[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 43a1869)
1 parent 2eb7ed5 commit 7d7fa9d

2 files changed

Lines changed: 36 additions & 8 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :meth:`curses.window.instr` returning one character too few when the
2+
:mod:`curses` module is built against a curses library that counts the
3+
terminator in the requested length, such as the NetBSD one.

Modules/_cursesmodule.c

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ static int initialisedcolors = FALSE;
185185
while these functions are still in use. */
186186
static char *screen_encoding = NULL;
187187

188+
/* ncurses and PDCurses store n characters and add a terminator; NetBSD
189+
curses counts the terminator in n. Ask an unknown library for one more. */
190+
#if defined(NCURSES_VERSION) || defined(PDCURSES)
191+
# define CURSES_STR_EXTRA 0
192+
#else
193+
# define CURSES_STR_EXTRA 1
194+
#endif
195+
188196
/* Utility Macros */
189197
#define PyCursesSetupTermCalled \
190198
if (initialised_setupterm != TRUE) { \
@@ -1839,13 +1847,13 @@ bytes are read.
18391847
static PyObject *
18401848
PyCursesWindow_InStr(PyCursesWindowObject *self, PyObject *args)
18411849
{
1842-
int x, y, n;
1850+
int x = 0, y = 0, n;
18431851
char rtn[1024]; /* This should be big enough.. I hope */
1844-
int rtn2;
1852+
int rtn2, use_xy = 0;
18451853

18461854
switch (PyTuple_Size(args)) {
18471855
case 0:
1848-
rtn2 = winnstr(self->win,rtn, 1023);
1856+
n = 1023;
18491857
break;
18501858
case 1:
18511859
if (!PyArg_ParseTuple(args,"i;n", &n))
@@ -1854,12 +1862,13 @@ PyCursesWindow_InStr(PyCursesWindowObject *self, PyObject *args)
18541862
PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative");
18551863
return NULL;
18561864
}
1857-
rtn2 = winnstr(self->win, rtn, Py_MIN(n, 1023));
1865+
n = Py_MIN(n, 1023);
18581866
break;
18591867
case 2:
18601868
if (!PyArg_ParseTuple(args,"ii;y,x",&y,&x))
18611869
return NULL;
1862-
rtn2 = mvwinnstr(self->win,y,x,rtn,1023);
1870+
n = 1023;
1871+
use_xy = 1;
18631872
break;
18641873
case 3:
18651874
if (!PyArg_ParseTuple(args, "iii;y,x,n", &y, &x, &n))
@@ -1868,15 +1877,31 @@ PyCursesWindow_InStr(PyCursesWindowObject *self, PyObject *args)
18681877
PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative");
18691878
return NULL;
18701879
}
1871-
rtn2 = mvwinnstr(self->win, y, x, rtn, Py_MIN(n,1023));
1880+
n = Py_MIN(n, 1023);
1881+
use_xy = 1;
18721882
break;
18731883
default:
18741884
PyErr_SetString(PyExc_TypeError, "instr requires 0 or 3 arguments");
18751885
return NULL;
18761886
}
1887+
1888+
/* Read again if the library stored more than asked: truncating could
1889+
split a multibyte character. */
1890+
for (int ask = n + CURSES_STR_EXTRA; ; ask = n) {
1891+
if (use_xy) {
1892+
rtn2 = mvwinnstr(self->win, y, x, rtn, ask);
1893+
}
1894+
else {
1895+
rtn2 = winnstr(self->win, rtn, ask);
1896+
}
1897+
if (rtn2 == ERR || rtn2 <= n) {
1898+
break;
1899+
}
1900+
}
1901+
18771902
if (rtn2 == ERR)
1878-
rtn[0] = 0;
1879-
return PyBytes_FromString(rtn);
1903+
return PyBytes_FromStringAndSize(NULL, 0);
1904+
return PyBytes_FromStringAndSize(rtn, rtn2);
18801905
}
18811906

18821907
/*[clinic input]

0 commit comments

Comments
 (0)