gh-151776: Add curses state-query functions (GH-151778)

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) <noreply@anthropic.com>
This commit is contained in:
Serhiy Storchaka 2026-06-24 22:31:50 +03:00 committed by GitHub
parent e0909f0f09
commit a52f428fba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 502 additions and 1 deletions

View file

@ -398,6 +398,41 @@ 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.
Availability: ncurses 6.5 or later.
.. versionadded:: next
.. function:: is_echo()
Return ``True`` if echo mode (see :func:`echo`) is enabled,
``False`` otherwise.
Availability: ncurses 6.5 or later.
.. versionadded:: next
.. 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.
Availability: ncurses 6.5 or later.
.. versionadded:: next
.. function:: is_term_resized(nlines, ncols)
Return ``True`` if :func:`resize_term` would modify the window structure,
@ -1154,6 +1189,16 @@ 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`
@ -1167,6 +1212,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
@ -1174,6 +1227,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)
@ -1319,6 +1380,48 @@ Window objects
.. versionadded:: next
.. 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
@ -1326,6 +1429,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

View file

@ -125,6 +125,12 @@ curses
support.
(Contributed by Serhiy Storchaka in :gh:`151774`.)
* 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
----

View file

@ -1028,6 +1028,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())

View file

@ -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``.

View file

@ -900,6 +900,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)
@ -3373,12 +3419,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__
@ -3428,6 +3490,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."},
@ -3897,6 +3997,65 @@ _curses_cbreak_impl(PyObject *module, int flag)
/*[clinic end generated code: output=9f9dee9664769751 input=42d81687f11ddbf3]*/
NoArgOrFlagNoReturnFunctionBody(cbreak, flag)
/* 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
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
@ -6371,6 +6530,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

View file

@ -1991,6 +1991,94 @@ exit:
return return_value;
}
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427)
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 >= 20240427) */
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427)
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 >= 20240427) */
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427)
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 >= 20240427) */
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427)
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 >= 20240427) */
PyDoc_STRVAR(_curses_color_content__doc__,
"color_content($module, color_number, /)\n"
"--\n"
@ -4751,6 +4839,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_ERASEWCHAR_METHODDEF
#define _CURSES_ERASEWCHAR_METHODDEF
#endif /* !defined(_CURSES_ERASEWCHAR_METHODDEF) */
@ -4862,4 +4966,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=35a3d93708112587 input=a9049054013a1b77]*/
/*[clinic end generated code: output=fd0f4e65dc594a65 input=a9049054013a1b77]*/

View file

@ -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),