From 58270bb429d32952732f6e8bc56c1b03ed97f45c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 20 Jun 2026 12:11:09 +0300 Subject: [PATCH 1/3] gh-151776: Add curses state-query functions Add window methods and module functions that report curses state which could previously only be set: the window getters is_cleared(), is_idcok(), is_idlok(), is_immedok(), is_keypad(), is_leaveok(), is_nodelay(), is_notimeout(), is_pad(), is_scrollok(), is_subwin(), is_syncok(), getdelay(), getparent() and getscrreg(), and the functions is_cbreak(), is_echo(), is_nl() and is_raw(). They are available when built against an ncurses with NCURSES_EXT_FUNCS. Co-Authored-By: Claude Opus 4.8 (1M context) --- Doc/library/curses.rst | 141 +++++++++++++++ Doc/whatsnew/3.16.rst | 6 + Lib/test/test_curses.py | 69 ++++++++ ...-06-20-12-10-02.gh-issue-151776.BtsXIF.rst | 12 ++ Modules/_cursesmodule.c | 162 ++++++++++++++++++ Modules/clinic/_cursesmodule.c.h | 106 +++++++++++- 6 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-20-12-10-02.gh-issue-151776.BtsXIF.rst diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index d7873054d6b9154..f8bfcfe0d4fa2dc 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -348,6 +348,37 @@ The module :mod:`!curses` defines the following functions: no flushing is done. +.. function:: is_cbreak() + + Return ``True`` if cbreak mode (see :func:`cbreak`) is enabled, ``False`` + otherwise. + + .. versionadded:: next + + +.. function:: is_echo() + + Return ``True`` if echo mode (see :func:`echo`) is enabled, ``False`` + otherwise. + + .. versionadded:: next + + +.. function:: is_nl() + + Return ``True`` if nl mode (see :func:`nl`) is enabled, ``False`` otherwise. + + .. versionadded:: next + + +.. function:: is_raw() + + Return ``True`` if raw mode (see :func:`raw`) is enabled, ``False`` + otherwise. + + .. versionadded:: next + + .. function:: is_term_resized(nlines, ncols) Return ``True`` if :func:`resize_term` would modify the window structure, @@ -1005,6 +1036,15 @@ Window objects .. versionadded:: 3.3 +.. method:: window.getdelay() + + Return the window's read timeout in milliseconds, as set by :meth:`nodelay` + or :meth:`timeout`: ``-1`` for blocking, ``0`` for non-blocking, or a + positive number of milliseconds. + + .. versionadded:: next + + .. method:: window.getkey([y, x]) Get a character, returning a string instead of an integer, as :meth:`getch` @@ -1018,6 +1058,14 @@ Window objects Return a tuple ``(y, x)`` of the height and width of the window. +.. method:: window.getparent() + + Return the parent window of this subwindow, or ``None`` if this window is + not a subwindow. + + .. versionadded:: next + + .. method:: window.getparyx() Return the beginning coordinates of this window relative to its parent window @@ -1025,6 +1073,14 @@ Window objects parent. +.. method:: window.getscrreg() + + Return a tuple ``(top, bottom)`` of the window's current scrolling region, + as set by :meth:`setscrreg`. + + .. versionadded:: next + + .. method:: window.getstr() window.getstr(n) window.getstr(y, x) @@ -1137,6 +1193,48 @@ Window objects The maximum value for *n* was increased from 1023 to 2047. +.. method:: window.is_cleared() + + Return the current value set by :meth:`clearok`. + + .. versionadded:: next + + +.. method:: window.is_idcok() + + Return the current value set by :meth:`idcok`. + + .. versionadded:: next + + +.. method:: window.is_idlok() + + Return the current value set by :meth:`idlok`. + + .. versionadded:: next + + +.. method:: window.is_immedok() + + Return the current value set by :meth:`immedok`. + + .. versionadded:: next + + +.. method:: window.is_keypad() + + Return the current value set by :meth:`keypad`. + + .. versionadded:: next + + +.. method:: window.is_leaveok() + + Return the current value set by :meth:`leaveok`. + + .. versionadded:: next + + .. method:: window.is_linetouched(line) Return ``True`` if the specified line was modified since the last call to @@ -1144,6 +1242,49 @@ Window objects exception if *line* is not valid for the given window. +.. method:: window.is_nodelay() + + Return the current value set by :meth:`nodelay`. + + .. versionadded:: next + + +.. method:: window.is_notimeout() + + Return the current value set by :meth:`notimeout`. + + .. versionadded:: next + + +.. method:: window.is_pad() + + Return ``True`` if the window is a pad created by :func:`newpad`. + + .. versionadded:: next + + +.. method:: window.is_scrollok() + + Return the current value set by :meth:`scrollok`. + + .. versionadded:: next + + +.. method:: window.is_subwin() + + Return ``True`` if the window is a subwindow created by :meth:`subwin` + or :meth:`derwin`. + + .. versionadded:: next + + +.. method:: window.is_syncok() + + Return the current value set by :meth:`syncok`. + + .. versionadded:: next + + .. method:: window.is_wintouched() Return ``True`` if the specified window was modified since the last call to diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 2b6f396bdf16dd9..be683b1723bae7b 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -92,6 +92,12 @@ curses * Add :func:`curses.nofilter`, which undoes the effect of :func:`curses.filter`. (Contributed by Serhiy Storchaka in :gh:`151744`.) +* Add :mod:`curses` functions and window methods that report state which could + previously only be set, such as :meth:`curses.window.is_keypad`, + :meth:`curses.window.getparent` and :func:`curses.is_cbreak`, + available when built against an ncurses with ``NCURSES_EXT_FUNCS``. + (Contributed by Serhiy Storchaka in :gh:`151776`.) + gzip ---- diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 98f1a7c8a0a2c5c..0aac76dd720a816 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -890,6 +890,75 @@ def test_input_options(self): stdscr.timeout(0) stdscr.timeout(5) + @requires_curses_window_meth('is_scrollok') + def test_state_getters(self): + stdscr = self.stdscr + # Each is_*() getter returns the value set by the matching setter. + for setter, getter in [ + ('clearok', 'is_cleared'), + ('idcok', 'is_idcok'), + ('idlok', 'is_idlok'), + ('keypad', 'is_keypad'), + ('leaveok', 'is_leaveok'), + ('nodelay', 'is_nodelay'), + ('notimeout', 'is_notimeout'), + ('scrollok', 'is_scrollok'), + ]: + getattr(stdscr, setter)(True) + self.assertIs(getattr(stdscr, getter)(), True) + getattr(stdscr, setter)(False) + self.assertIs(getattr(stdscr, getter)(), False) + if hasattr(stdscr, 'immedok'): + stdscr.immedok(True) + self.assertIs(stdscr.is_immedok(), True) + stdscr.immedok(False) + if hasattr(stdscr, 'syncok'): + stdscr.syncok(True) + self.assertIs(stdscr.is_syncok(), True) + stdscr.syncok(False) + + # getdelay() reflects timeout()/nodelay(). + stdscr.timeout(100) + self.assertEqual(stdscr.getdelay(), 100) + stdscr.nodelay(True) + self.assertEqual(stdscr.getdelay(), 0) + stdscr.timeout(-1) + self.assertEqual(stdscr.getdelay(), -1) + + # getscrreg() reflects setscrreg(). + stdscr.setscrreg(5, 10) + self.assertEqual(stdscr.getscrreg(), (5, 10)) + + # is_pad()/is_subwin()/getparent(). + self.assertIs(stdscr.is_pad(), False) + self.assertIs(stdscr.is_subwin(), False) + self.assertIsNone(stdscr.getparent()) + sub = stdscr.subwin(3, 3, 0, 0) + self.assertIs(sub.is_subwin(), True) + self.assertIs(sub.getparent(), stdscr) + pad = curses.newpad(5, 5) + self.assertIs(pad.is_pad(), True) + + @requires_curses_func('is_cbreak') + def test_global_state_getters(self): + if self.isatty: + curses.cbreak() + self.assertIs(curses.is_cbreak(), True) + curses.nocbreak() + self.assertIs(curses.is_cbreak(), False) + curses.raw() + self.assertIs(curses.is_raw(), True) + curses.noraw() + self.assertIs(curses.is_raw(), False) + curses.echo() + self.assertIs(curses.is_echo(), True) + curses.noecho() + self.assertIs(curses.is_echo(), False) + curses.nl() + self.assertIs(curses.is_nl(), True) + curses.nonl() + self.assertIs(curses.is_nl(), False) + @requires_curses_func('typeahead') def test_typeahead(self): curses.typeahead(sys.__stdin__.fileno()) diff --git a/Misc/NEWS.d/next/Library/2026-06-20-12-10-02.gh-issue-151776.BtsXIF.rst b/Misc/NEWS.d/next/Library/2026-06-20-12-10-02.gh-issue-151776.BtsXIF.rst new file mode 100644 index 000000000000000..3eabc4a6bea82b5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-20-12-10-02.gh-issue-151776.BtsXIF.rst @@ -0,0 +1,12 @@ +Add :mod:`curses` functions and window methods that report state which could +previously only be set: the window methods :meth:`~curses.window.is_cleared`, +:meth:`~curses.window.is_idcok`, :meth:`~curses.window.is_idlok`, +:meth:`~curses.window.is_immedok`, :meth:`~curses.window.is_keypad`, +:meth:`~curses.window.is_leaveok`, :meth:`~curses.window.is_nodelay`, +:meth:`~curses.window.is_notimeout`, :meth:`~curses.window.is_pad`, +:meth:`~curses.window.is_scrollok`, :meth:`~curses.window.is_subwin`, +:meth:`~curses.window.is_syncok`, :meth:`~curses.window.getdelay`, +:meth:`~curses.window.getparent` and :meth:`~curses.window.getscrreg`, and the +functions :func:`curses.is_cbreak`, :func:`curses.is_echo`, +:func:`curses.is_nl` and :func:`curses.is_raw`. They are only available when +built against an ncurses with ``NCURSES_EXT_FUNCS``. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index e60cba3ef87ead1..9f6badb2167cde2 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -844,6 +844,52 @@ Window_NoArgNoReturnFunction(wdeleteln) Window_NoArgTrueFalseFunction(is_wintouched) +#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404 +Window_NoArgTrueFalseFunction(is_cleared) +Window_NoArgTrueFalseFunction(is_idcok) +Window_NoArgTrueFalseFunction(is_idlok) +Window_NoArgTrueFalseFunction(is_immedok) +Window_NoArgTrueFalseFunction(is_keypad) +Window_NoArgTrueFalseFunction(is_leaveok) +Window_NoArgTrueFalseFunction(is_nodelay) +Window_NoArgTrueFalseFunction(is_notimeout) +Window_NoArgTrueFalseFunction(is_pad) +Window_NoArgTrueFalseFunction(is_scrollok) +Window_NoArgTrueFalseFunction(is_subwin) +Window_NoArgTrueFalseFunction(is_syncok) + +static PyObject * +PyCursesWindow_getdelay(PyObject *op, PyObject *Py_UNUSED(ignored)) +{ + PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op); + return PyLong_FromLong(wgetdelay(self->win)); +} + +static PyObject * +PyCursesWindow_getscrreg(PyObject *op, PyObject *Py_UNUSED(ignored)) +{ + PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op); + int top, bottom; + if (wgetscrreg(self->win, &top, &bottom) == ERR) { + curses_window_set_error(self, "wgetscrreg", "getscrreg"); + return NULL; + } + return Py_BuildValue("(ii)", top, bottom); +} + +static PyObject * +PyCursesWindow_getparent(PyObject *op, PyObject *Py_UNUSED(ignored)) +{ + PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op); + /* The standard window has no parent; subwindows keep a reference to the + window they were derived from. */ + if (self->orig == NULL) { + Py_RETURN_NONE; + } + return Py_NewRef((PyObject *)self->orig); +} +#endif /* NCURSES_EXT_FUNCS */ + Window_NoArgNoReturnVoidFunction(wsyncup) Window_NoArgNoReturnVoidFunction(wsyncdown) Window_NoArgNoReturnVoidFunction(wstandend) @@ -2973,12 +3019,28 @@ static PyMethodDef PyCursesWindow_methods[] = { _CURSES_WINDOW_GETCH_METHODDEF _CURSES_WINDOW_GETKEY_METHODDEF _CURSES_WINDOW_GET_WCH_METHODDEF +#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404 + {"getdelay", PyCursesWindow_getdelay, METH_NOARGS, + "getdelay($self, /)\n--\n\n" + "Return the window's read timeout in milliseconds.\n\n" + "-1 means blocking, 0 means non-blocking; see nodelay() and timeout()."}, +#endif {"getmaxyx", PyCursesWindow_getmaxyx, METH_NOARGS, "getmaxyx($self, /)\n--\n\n" "Return a tuple (y, x) of the window height and width."}, +#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404 + {"getparent", PyCursesWindow_getparent, METH_NOARGS, + "getparent($self, /)\n--\n\n" + "Return the parent window, or None if this is not a subwindow."}, +#endif {"getparyx", PyCursesWindow_getparyx, METH_NOARGS, "getparyx($self, /)\n--\n\n" "Return (y, x) relative to the parent window, or (-1, -1) if none."}, +#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404 + {"getscrreg", PyCursesWindow_getscrreg, METH_NOARGS, + "getscrreg($self, /)\n--\n\n" + "Return a tuple (top, bottom) of the current scrolling region."}, +#endif { "getstr", PyCursesWindow_getstr, METH_VARARGS, _curses_window_getstr__doc__ @@ -3016,6 +3078,44 @@ static PyMethodDef PyCursesWindow_methods[] = { {"is_wintouched", PyCursesWindow_is_wintouched, METH_NOARGS, "is_wintouched($self, /)\n--\n\n" "Return True if the window changed since the last refresh()."}, +#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404 + {"is_cleared", PyCursesWindow_is_cleared, METH_NOARGS, + "is_cleared($self, /)\n--\n\n" + "Return the current value set by clearok()."}, + {"is_idcok", PyCursesWindow_is_idcok, METH_NOARGS, + "is_idcok($self, /)\n--\n\n" + "Return the current value set by idcok()."}, + {"is_idlok", PyCursesWindow_is_idlok, METH_NOARGS, + "is_idlok($self, /)\n--\n\n" + "Return the current value set by idlok()."}, + {"is_immedok", PyCursesWindow_is_immedok, METH_NOARGS, + "is_immedok($self, /)\n--\n\n" + "Return the current value set by immedok()."}, + {"is_keypad", PyCursesWindow_is_keypad, METH_NOARGS, + "is_keypad($self, /)\n--\n\n" + "Return the current value set by keypad()."}, + {"is_leaveok", PyCursesWindow_is_leaveok, METH_NOARGS, + "is_leaveok($self, /)\n--\n\n" + "Return the current value set by leaveok()."}, + {"is_nodelay", PyCursesWindow_is_nodelay, METH_NOARGS, + "is_nodelay($self, /)\n--\n\n" + "Return the current value set by nodelay()."}, + {"is_notimeout", PyCursesWindow_is_notimeout, METH_NOARGS, + "is_notimeout($self, /)\n--\n\n" + "Return the current value set by notimeout()."}, + {"is_pad", PyCursesWindow_is_pad, METH_NOARGS, + "is_pad($self, /)\n--\n\n" + "Return True if the window is a pad."}, + {"is_scrollok", PyCursesWindow_is_scrollok, METH_NOARGS, + "is_scrollok($self, /)\n--\n\n" + "Return the current value set by scrollok()."}, + {"is_subwin", PyCursesWindow_is_subwin, METH_NOARGS, + "is_subwin($self, /)\n--\n\n" + "Return True if the window is a subwindow."}, + {"is_syncok", PyCursesWindow_is_syncok, METH_NOARGS, + "is_syncok($self, /)\n--\n\n" + "Return the current value set by syncok()."}, +#endif {"keypad", PyCursesWindow_keypad, METH_VARARGS, "keypad($self, flag, /)\n--\n\n" "Interpret escape sequences for special keys if flag is true."}, @@ -3298,6 +3398,64 @@ _curses_cbreak_impl(PyObject *module, int flag) /*[clinic end generated code: output=9f9dee9664769751 input=42d81687f11ddbf3]*/ NoArgOrFlagNoReturnFunctionBody(cbreak, flag) +#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404 +/*[clinic input] +_curses.is_cbreak + +Return True if cbreak mode is enabled, False otherwise. +[clinic start generated code]*/ + +static PyObject * +_curses_is_cbreak_impl(PyObject *module) +/*[clinic end generated code: output=8a1ad7889fb43daf input=99988df6fd2f1c81]*/ +{ + PyCursesStatefulInitialised(module); + return PyBool_FromLong(is_cbreak()); +} + +/*[clinic input] +_curses.is_echo + +Return True if echo mode is enabled, False otherwise. +[clinic start generated code]*/ + +static PyObject * +_curses_is_echo_impl(PyObject *module) +/*[clinic end generated code: output=72692d2aa41591c4 input=f6152cf7c00e47eb]*/ +{ + PyCursesStatefulInitialised(module); + return PyBool_FromLong(is_echo()); +} + +/*[clinic input] +_curses.is_nl + +Return True if nl mode is enabled, False otherwise. +[clinic start generated code]*/ + +static PyObject * +_curses_is_nl_impl(PyObject *module) +/*[clinic end generated code: output=999eb44abc43ce65 input=1e0a2607e45a01e1]*/ +{ + PyCursesStatefulInitialised(module); + return PyBool_FromLong(is_nl()); +} + +/*[clinic input] +_curses.is_raw + +Return True if raw mode is enabled, False otherwise. +[clinic start generated code]*/ + +static PyObject * +_curses_is_raw_impl(PyObject *module) +/*[clinic end generated code: output=dd9816d777561c35 input=a64fa6a251ed3ece]*/ +{ + PyCursesStatefulInitialised(module); + return PyBool_FromLong(is_raw()); +} +#endif /* NCURSES_EXT_FUNCS */ + /*[clinic input] _curses.color_content @@ -5360,6 +5518,10 @@ static PyMethodDef cursesmodule_methods[] = { _CURSES_INIT_PAIR_METHODDEF _CURSES_INITSCR_METHODDEF _CURSES_INTRFLUSH_METHODDEF + _CURSES_IS_CBREAK_METHODDEF + _CURSES_IS_ECHO_METHODDEF + _CURSES_IS_NL_METHODDEF + _CURSES_IS_RAW_METHODDEF _CURSES_ISENDWIN_METHODDEF _CURSES_IS_TERM_RESIZED_METHODDEF _CURSES_KEYNAME_METHODDEF diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h index f577368680ef572..fb9ab27d4364c36 100644 --- a/Modules/clinic/_cursesmodule.c.h +++ b/Modules/clinic/_cursesmodule.c.h @@ -1991,6 +1991,94 @@ _curses_cbreak(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } +#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) + +PyDoc_STRVAR(_curses_is_cbreak__doc__, +"is_cbreak($module, /)\n" +"--\n" +"\n" +"Return True if cbreak mode is enabled, False otherwise."); + +#define _CURSES_IS_CBREAK_METHODDEF \ + {"is_cbreak", (PyCFunction)_curses_is_cbreak, METH_NOARGS, _curses_is_cbreak__doc__}, + +static PyObject * +_curses_is_cbreak_impl(PyObject *module); + +static PyObject * +_curses_is_cbreak(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _curses_is_cbreak_impl(module); +} + +#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) */ + +#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) + +PyDoc_STRVAR(_curses_is_echo__doc__, +"is_echo($module, /)\n" +"--\n" +"\n" +"Return True if echo mode is enabled, False otherwise."); + +#define _CURSES_IS_ECHO_METHODDEF \ + {"is_echo", (PyCFunction)_curses_is_echo, METH_NOARGS, _curses_is_echo__doc__}, + +static PyObject * +_curses_is_echo_impl(PyObject *module); + +static PyObject * +_curses_is_echo(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _curses_is_echo_impl(module); +} + +#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) */ + +#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) + +PyDoc_STRVAR(_curses_is_nl__doc__, +"is_nl($module, /)\n" +"--\n" +"\n" +"Return True if nl mode is enabled, False otherwise."); + +#define _CURSES_IS_NL_METHODDEF \ + {"is_nl", (PyCFunction)_curses_is_nl, METH_NOARGS, _curses_is_nl__doc__}, + +static PyObject * +_curses_is_nl_impl(PyObject *module); + +static PyObject * +_curses_is_nl(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _curses_is_nl_impl(module); +} + +#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) */ + +#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) + +PyDoc_STRVAR(_curses_is_raw__doc__, +"is_raw($module, /)\n" +"--\n" +"\n" +"Return True if raw mode is enabled, False otherwise."); + +#define _CURSES_IS_RAW_METHODDEF \ + {"is_raw", (PyCFunction)_curses_is_raw, METH_NOARGS, _curses_is_raw__doc__}, + +static PyObject * +_curses_is_raw_impl(PyObject *module); + +static PyObject * +_curses_is_raw(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _curses_is_raw_impl(module); +} + +#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) */ + PyDoc_STRVAR(_curses_color_content__doc__, "color_content($module, color_number, /)\n" "--\n" @@ -4437,6 +4525,22 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored #define _CURSES_NOFILTER_METHODDEF #endif /* !defined(_CURSES_NOFILTER_METHODDEF) */ +#ifndef _CURSES_IS_CBREAK_METHODDEF + #define _CURSES_IS_CBREAK_METHODDEF +#endif /* !defined(_CURSES_IS_CBREAK_METHODDEF) */ + +#ifndef _CURSES_IS_ECHO_METHODDEF + #define _CURSES_IS_ECHO_METHODDEF +#endif /* !defined(_CURSES_IS_ECHO_METHODDEF) */ + +#ifndef _CURSES_IS_NL_METHODDEF + #define _CURSES_IS_NL_METHODDEF +#endif /* !defined(_CURSES_IS_NL_METHODDEF) */ + +#ifndef _CURSES_IS_RAW_METHODDEF + #define _CURSES_IS_RAW_METHODDEF +#endif /* !defined(_CURSES_IS_RAW_METHODDEF) */ + #ifndef _CURSES_GETSYX_METHODDEF #define _CURSES_GETSYX_METHODDEF #endif /* !defined(_CURSES_GETSYX_METHODDEF) */ @@ -4516,4 +4620,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored #ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */ -/*[clinic end generated code: output=7494804bf2c4d1f5 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3b988826847b3985 input=a9049054013a1b77]*/ From c1d7b20346961f5d33099363df2fff331e6bb5eb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 20 Jun 2026 12:26:08 +0300 Subject: [PATCH 2/3] Build is_cbreak()/is_echo()/is_nl()/is_raw() only on ncurses 6.5+ These four functions were added in ncurses 6.5; guarding them with the same NCURSES_EXT_FUNCS >= 20110404 as the window getters broke the build on older ncurses (e.g. macOS, which ships 6.0). Require NCURSES_EXT_FUNCS >= 20240427. Co-Authored-By: Claude Opus 4.8 (1M context) --- Doc/library/curses.rst | 27 ++++++++++++++++----------- Modules/_cursesmodule.c | 3 ++- Modules/clinic/_cursesmodule.c.h | 18 +++++++++--------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index f8bfcfe0d4fa2dc..3ab937cad347731 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -350,16 +350,18 @@ The module :mod:`!curses` defines the following functions: .. function:: is_cbreak() - Return ``True`` if cbreak mode (see :func:`cbreak`) is enabled, ``False`` - otherwise. + Return ``True`` if cbreak mode (see :func:`cbreak`) is enabled, + ``False`` otherwise. + Availability: ncurses 6.5 or later. .. versionadded:: next .. function:: is_echo() - Return ``True`` if echo mode (see :func:`echo`) is enabled, ``False`` - otherwise. + Return ``True`` if echo mode (see :func:`echo`) is enabled, + ``False`` otherwise. + Availability: ncurses 6.5 or later. .. versionadded:: next @@ -367,14 +369,16 @@ The module :mod:`!curses` defines the following functions: .. function:: is_nl() Return ``True`` if nl mode (see :func:`nl`) is enabled, ``False`` otherwise. + Availability: ncurses 6.5 or later. .. versionadded:: next .. function:: is_raw() - Return ``True`` if raw mode (see :func:`raw`) is enabled, ``False`` - otherwise. + Return ``True`` if raw mode (see :func:`raw`) is enabled, + ``False`` otherwise. + Availability: ncurses 6.5 or later. .. versionadded:: next @@ -1038,9 +1042,10 @@ Window objects .. method:: window.getdelay() - Return the window's read timeout in milliseconds, as set by :meth:`nodelay` - or :meth:`timeout`: ``-1`` for blocking, ``0`` for non-blocking, or a - positive number of milliseconds. + Return the window's read timeout in milliseconds, + as set by :meth:`nodelay` or :meth:`timeout`: + ``-1`` for blocking, ``0`` for non-blocking, + or a positive number of milliseconds. .. versionadded:: next @@ -1060,8 +1065,8 @@ Window objects .. method:: window.getparent() - Return the parent window of this subwindow, or ``None`` if this window is - not a subwindow. + Return the parent window of this subwindow, + or ``None`` if this window is not a subwindow. .. versionadded:: next diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 9f6badb2167cde2..9671e505aeded58 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -3398,7 +3398,8 @@ _curses_cbreak_impl(PyObject *module, int flag) /*[clinic end generated code: output=9f9dee9664769751 input=42d81687f11ddbf3]*/ NoArgOrFlagNoReturnFunctionBody(cbreak, flag) -#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404 +/* is_cbreak()/is_echo()/is_nl()/is_raw() were added in ncurses 6.5. */ +#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427 /*[clinic input] _curses.is_cbreak diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h index fb9ab27d4364c36..167043292f07269 100644 --- a/Modules/clinic/_cursesmodule.c.h +++ b/Modules/clinic/_cursesmodule.c.h @@ -1991,7 +1991,7 @@ _curses_cbreak(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } -#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) +#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427) PyDoc_STRVAR(_curses_is_cbreak__doc__, "is_cbreak($module, /)\n" @@ -2011,9 +2011,9 @@ _curses_is_cbreak(PyObject *module, PyObject *Py_UNUSED(ignored)) return _curses_is_cbreak_impl(module); } -#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) */ +#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427) */ -#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) +#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427) PyDoc_STRVAR(_curses_is_echo__doc__, "is_echo($module, /)\n" @@ -2033,9 +2033,9 @@ _curses_is_echo(PyObject *module, PyObject *Py_UNUSED(ignored)) return _curses_is_echo_impl(module); } -#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) */ +#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427) */ -#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) +#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427) PyDoc_STRVAR(_curses_is_nl__doc__, "is_nl($module, /)\n" @@ -2055,9 +2055,9 @@ _curses_is_nl(PyObject *module, PyObject *Py_UNUSED(ignored)) return _curses_is_nl_impl(module); } -#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) */ +#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427) */ -#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) +#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427) PyDoc_STRVAR(_curses_is_raw__doc__, "is_raw($module, /)\n" @@ -2077,7 +2077,7 @@ _curses_is_raw(PyObject *module, PyObject *Py_UNUSED(ignored)) return _curses_is_raw_impl(module); } -#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) */ +#endif /* (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427) */ PyDoc_STRVAR(_curses_color_content__doc__, "color_content($module, color_number, /)\n" @@ -4620,4 +4620,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored #ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */ -/*[clinic end generated code: output=3b988826847b3985 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=68f33f61fb666127 input=a9049054013a1b77]*/ From 6670403d7ed0d9c918cea9906259632dc3dcc8da Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 24 Jun 2026 20:05:44 +0300 Subject: [PATCH 3/3] gh-151776: Raise the c-analyzer size limit for _cursesmodule.c The new window state-query methods grew PyCursesWindow_methods[] past the default 200-line limit, crashing check-c-globals. Co-Authored-By: Claude Opus 4.8 (1M context) --- Tools/c-analyzer/cpython/_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py index d7248c34c59be45..2875f45cb8d3756 100644 --- a/Tools/c-analyzer/cpython/_parser.py +++ b/Tools/c-analyzer/cpython/_parser.py @@ -310,6 +310,7 @@ def format_tsv_lines(lines): # default: (10_000, 200) # First match wins. _abs('Modules/_ctypes/ctypes.h'): (5_000, 500), + _abs('Modules/_cursesmodule.c'): (20_000, 300), _abs('Modules/_datetimemodule.c'): (20_000, 300), _abs('Modules/_hacl/*.c'): (200_000, 500), _abs('Modules/posixmodule.c'): (20_000, 500),