@@ -309,6 +309,14 @@ curses_window_set_null_error(PyCursesWindowObject *win,
309309 _curses_set_null_error (state , curses_funcname , python_funcname );
310310}
311311
312+ /* ncurses and PDCurses store n characters and add a terminator; NetBSD
313+ curses counts the terminator in n. Ask an unknown library for one more. */
314+ #if defined(NCURSES_VERSION ) || defined(PDCURSES )
315+ # define CURSES_STR_EXTRA 0
316+ #else
317+ # define CURSES_STR_EXTRA 1
318+ #endif
319+
312320/* Utility Checking Procedures */
313321
314322/*
@@ -3796,25 +3804,33 @@ curses_window_instr_bytes(PyCursesWindowObject *self, PyObject *args,
37963804 return NULL ;
37973805 }
37983806
3799- n = Py_MIN (n , max_buf_size - 1 );
3807+ n = Py_MIN (n , max_buf_size - 1 - CURSES_STR_EXTRA );
3808+ n += CURSES_STR_EXTRA ;
38003809 PyBytesWriter * writer = PyBytesWriter_Create (n + 1 );
38013810 if (writer == NULL ) {
38023811 return NULL ;
38033812 }
38043813 char * buf = PyBytesWriter_GetData (writer );
38053814
3806- if (use_xy ) {
3807- rtn = mvwinnstr (self -> win , y , x , buf , n );
3808- }
3809- else {
3810- rtn = winnstr (self -> win , buf , n );
3815+ /* Read again if the library stored more than asked: truncating could
3816+ split a multibyte character. */
3817+ for (unsigned int want = n - CURSES_STR_EXTRA ; ; n = want ) {
3818+ if (use_xy ) {
3819+ rtn = mvwinnstr (self -> win , y , x , buf , n );
3820+ }
3821+ else {
3822+ rtn = winnstr (self -> win , buf , n );
3823+ }
3824+ if (rtn == ERR || (unsigned int )rtn <= want ) {
3825+ break ;
3826+ }
38113827 }
38123828
38133829 if (rtn == ERR ) {
38143830 PyBytesWriter_Discard (writer );
38153831 return Py_GetConstant (Py_CONSTANT_EMPTY_BYTES );
38163832 }
3817- return PyBytesWriter_FinishWithSize (writer , strlen ( buf ) );
3833+ return PyBytesWriter_FinishWithSize (writer , rtn );
38183834}
38193835
38203836static PyObject *
@@ -3939,17 +3955,25 @@ PyCursesWindow_in_wstr(PyObject *op, PyObject *args)
39393955 return NULL ;
39403956 }
39413957
3942- n = Py_MIN (n , max_buf_size - 1 );
3958+ n = Py_MIN (n , max_buf_size - 1 - CURSES_STR_EXTRA );
3959+ n += CURSES_STR_EXTRA ;
39433960 wchar_t * buf = PyMem_New (wchar_t , n + 1 );
39443961 if (buf == NULL ) {
39453962 return PyErr_NoMemory ();
39463963 }
39473964
3948- if (use_xy ) {
3949- rtn = mvwinnwstr (self -> win , y , x , buf , n );
3950- }
3951- else {
3952- rtn = winnwstr (self -> win , buf , n );
3965+ /* Read again if the library stored more than asked: truncating could
3966+ separate a combining character from its base. */
3967+ for (unsigned int want = n - CURSES_STR_EXTRA ; ; n = want ) {
3968+ if (use_xy ) {
3969+ rtn = mvwinnwstr (self -> win , y , x , buf , n );
3970+ }
3971+ else {
3972+ rtn = winnwstr (self -> win , buf , n );
3973+ }
3974+ if (rtn == ERR || (unsigned int )rtn <= want ) {
3975+ break ;
3976+ }
39533977 }
39543978
39553979 if (rtn == ERR ) {
@@ -4003,7 +4027,8 @@ PyCursesWindow_in_wchstr(PyObject *op, PyObject *args)
40034027 return NULL ;
40044028 }
40054029
4006- n = Py_MIN (n , max_buf_size - 1 );
4030+ n = Py_MIN (n , max_buf_size - 1 - CURSES_STR_EXTRA );
4031+ n += CURSES_STR_EXTRA ;
40074032 cursesmodule_state * state = get_cursesmodule_state_by_win (self );
40084033 /* Zero the cells: reading a cell back through getcchar() relies on the
40094034 cchar_t text array being NUL-terminated, which some curses libraries
@@ -4026,6 +4051,7 @@ PyCursesWindow_in_wchstr(PyObject *op, PyObject *args)
40264051 return PyCursesComplexStr_New (state , NULL , 0 );
40274052 }
40284053
4054+ n -= CURSES_STR_EXTRA ;
40294055 /* win_wchnstr() stores at most n cells and zero-terminates the array at
40304056 the actual count; every real cell holds at least a space, so the first
40314057 empty cell marks the end of the run. */
@@ -4058,6 +4084,7 @@ PyCursesWindow_in_wchstr(PyObject *op, PyObject *args)
40584084 return PyCursesComplexStr_New (state , NULL , 0 );
40594085 }
40604086
4087+ n -= CURSES_STR_EXTRA ;
40614088 Py_ssize_t count = 0 ;
40624089 while (count < (Py_ssize_t )n && buf [count ] != 0 ) {
40634090 count ++ ;
0 commit comments