]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-152219: Add curses window attribute get/set methods and WA_* constants (GH-152221)
authorSerhiy Storchaka <storchaka@gmail.com>
Fri, 26 Jun 2026 04:48:53 +0000 (07:48 +0300)
committerGitHub <noreply@github.com>
Fri, 26 Jun 2026 04:48:53 +0000 (07:48 +0300)
Add the window methods attr_get(), attr_set(), attr_on(), attr_off() and
color_set(), wrapping wattr_get(), wattr_set(), wattr_on(), wattr_off() and
wcolor_set().  Unlike the legacy attron()/attroff()/attrset() methods, these
pass the color pair as a separate argument instead of packing it into the
attribute value.  Also add the corresponding WA_* attribute constants.

Add an attr_converter that range-checks the attr_t attribute argument and
raises OverflowError instead of silently truncating it; apply it to attr_set(),
attr_on(), attr_off() and chgat().

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Doc/library/curses.rst
Doc/whatsnew/3.16.rst
Lib/test/test_curses.py
Misc/NEWS.d/next/Library/2026-06-25-19-44-16.gh-issue-152219.ndj4Ib.rst [new file with mode: 0644]
Modules/_cursesmodule.c
Modules/clinic/_cursesmodule.c.h

index a8a9680d0703ecf0a943f65b716f2132d874443d..d7c2905ec7347da0d4b130223d2a639eaf72485f 100644 (file)
@@ -989,6 +989,55 @@ Window objects
    ``0`` (no attributes).
 
 
+.. method:: window.attr_get()
+
+   Return the window's current rendition as a ``(attrs, pair)`` tuple,
+   where *attrs* is the set of attributes and *pair* is the color pair number.
+
+   Unlike :meth:`attron` and friends, which take packed ``A_*`` attributes,
+   this method and the other ``attr_*`` methods work with the
+   :ref:`WA_* attributes <curses-wa-constants>` and keep the color pair as a
+   separate number, which lets them use color pairs that do not fit alongside
+   the attributes in a single value.
+
+   .. versionadded:: next
+
+
+.. method:: window.attr_set(attr, pair=0)
+
+   Set the window's rendition to the attributes *attr* and the color pair *pair*.
+
+   .. versionadded:: next
+
+
+.. method:: window.attr_on(attr)
+
+   Turn on the attributes *attr* without affecting any others.
+
+   .. versionadded:: next
+
+
+.. method:: window.attr_off(attr)
+
+   Turn off the attributes *attr* without affecting any others.
+
+   .. versionadded:: next
+
+
+.. method:: window.color_set(pair)
+
+   Set the window's color pair to *pair*, leaving the other attributes unchanged.
+
+   .. versionadded:: next
+
+
+.. method:: window.getattrs()
+
+   Return the window's current attributes.
+
+   .. versionadded:: next
+
+
 .. method:: window.bkgd(ch[, attr])
 
    Set the background property of the window to the character *ch*, with
@@ -1888,6 +1937,24 @@ The exact constants available are system dependent.
 .. versionadded:: 3.7
    ``A_ITALIC`` was added.
 
+.. _curses-wa-constants:
+
+The :meth:`~window.attr_get`, :meth:`~window.attr_set`, :meth:`~window.attr_on`
+and :meth:`~window.attr_off` methods use a parallel set of ``WA_*`` constants.
+These have the same meaning as the corresponding ``A_*`` attributes above
+(``WA_BOLD`` like :const:`A_BOLD`, and so on), but belong to the ``attr_t`` type
+rather than being packed into a character.  In ncurses the two sets share the
+same values, but other curses implementations may give them different ones, so
+use the ``WA_*`` constants with the ``attr_*`` methods.  The available names are
+``WA_ATTRIBUTES``, ``WA_NORMAL``, ``WA_STANDOUT``, ``WA_UNDERLINE``,
+``WA_REVERSE``, ``WA_BLINK``, ``WA_DIM``, ``WA_BOLD``, ``WA_ALTCHARSET``,
+``WA_INVIS``, ``WA_PROTECT``, ``WA_HORIZONTAL``, ``WA_LEFT``, ``WA_LOW``,
+``WA_RIGHT``, ``WA_TOP``, ``WA_VERTICAL`` and ``WA_ITALIC`` (each available only
+where the platform defines it).
+
+.. versionadded:: next
+   The ``WA_*`` constants were added.
+
 Several constants are available to extract corresponding attributes returned
 by some methods.
 
index cb6d5434fb3ce7d927c6631d2a1d218119b5a471..5123671db0c07eaf930f28bc23e461bb55ee44ba 100644 (file)
@@ -131,6 +131,13 @@ curses
   available when built against an ncurses with ``NCURSES_EXT_FUNCS``.
   (Contributed by Serhiy Storchaka in :gh:`151776`.)
 
+* Add the :mod:`curses` window methods :meth:`~curses.window.attr_get`,
+  :meth:`~curses.window.attr_set`, :meth:`~curses.window.attr_on`,
+  :meth:`~curses.window.attr_off` and :meth:`~curses.window.color_set`, which
+  pass the color pair as a separate argument instead of packing it into the
+  attribute value, and the corresponding ``WA_*`` attribute constants.
+  (Contributed by Serhiy Storchaka in :gh:`152219`.)
+
 gzip
 ----
 
index 721cde39861ce9747503a35fbd56a74e01e7620c..75e6d2bd62e88719b4dce5b720764610fb8e17ef 100644 (file)
@@ -651,7 +651,6 @@ class TestCurses(unittest.TestCase):
         win.scrollok(False)
 
     def test_attributes(self):
-        # TODO: attr_get(), attr_set(), ...
         win = curses.newwin(5, 15, 5, 2)
         win.attron(curses.A_BOLD)
         win.attroff(curses.A_BOLD)
@@ -660,6 +659,45 @@ class TestCurses(unittest.TestCase):
         win.standout()
         win.standend()
 
+        # The attr_*() family works on attr_t attributes paired with a color
+        # pair, unlike the chtype-based attron()/attroff()/attrset().
+        win.attr_set(curses.A_BOLD | curses.A_UNDERLINE)
+        attrs, pair = win.attr_get()
+        self.assertTrue(attrs & curses.A_BOLD)
+        self.assertTrue(attrs & curses.A_UNDERLINE)
+        self.assertEqual(pair, 0)
+        self.assertEqual(win.getattrs(), attrs)
+
+        win.attr_on(curses.A_REVERSE)
+        self.assertTrue(win.attr_get()[0] & curses.A_REVERSE)
+        win.attr_off(curses.A_REVERSE)
+        self.assertFalse(win.attr_get()[0] & curses.A_REVERSE)
+
+        # color_set() with a real pair needs start_color(); see
+        # test_attr_color_pair.  Here only the argument validation is checked,
+        # which fails before wcolor_set() is reached.
+        self.assertRaises(TypeError, win.attr_set, 'x')
+        self.assertRaises(TypeError, win.attr_set, curses.A_BOLD, 'x')
+        self.assertRaises(TypeError, win.attr_on, 'x')
+        self.assertRaises(TypeError, win.color_set, 'x')
+        self.assertRaises(ValueError, win.color_set, -1)
+        self.assertRaises(ValueError, win.attr_set, curses.A_BOLD, -1)
+        # attr_t is unsigned: a negative or too-large attribute overflows.
+        self.assertRaises(OverflowError, win.attr_set, -1)
+        self.assertRaises(OverflowError, win.attr_on, -1)
+        self.assertRaises(OverflowError, win.attr_set, 1 << 64)
+
+    @requires_colors
+    def test_attr_color_pair(self):
+        win = curses.newwin(5, 15, 5, 2)
+        curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
+        win.attr_set(curses.A_BOLD, 1)
+        attrs, pair = win.attr_get()
+        self.assertTrue(attrs & curses.A_BOLD)
+        self.assertEqual(pair, 1)
+        win.color_set(0)
+        self.assertEqual(win.attr_get()[1], 0)
+
     @requires_curses_window_meth('chgat')
     def test_chgat(self):
         win = curses.newwin(5, 15, 5, 2)
@@ -691,6 +729,11 @@ class TestCurses(unittest.TestCase):
         self.assertEqual(win.inch(3, 11), b'm'[0] | curses.A_UNDERLINE)
         self.assertEqual(win.inch(3, 14), b' '[0] | curses.A_UNDERLINE)
 
+        # attr_t is unsigned: a negative or too-large attribute overflows.
+        self.assertRaises(TypeError, win.chgat, 'x')
+        self.assertRaises(OverflowError, win.chgat, -1)
+        self.assertRaises(OverflowError, win.chgat, 1 << 64)
+
     def test_background(self):
         win = curses.newwin(5, 15, 5, 2)
         win.addstr(0, 0, 'Lorem ipsum')
diff --git a/Misc/NEWS.d/next/Library/2026-06-25-19-44-16.gh-issue-152219.ndj4Ib.rst b/Misc/NEWS.d/next/Library/2026-06-25-19-44-16.gh-issue-152219.ndj4Ib.rst
new file mode 100644 (file)
index 0000000..e7ea601
--- /dev/null
@@ -0,0 +1,5 @@
+Add the :mod:`curses` window methods :meth:`~curses.window.attr_get`,
+:meth:`~curses.window.attr_set`, :meth:`~curses.window.attr_on`,
+:meth:`~curses.window.attr_off` and :meth:`~curses.window.color_set`, which use
+a separate color pair argument instead of packing it into the attribute value,
+and the corresponding ``WA_*`` attribute constants.
index 3377367d4ce45df4cafcfdb133cdee7749e78b97..0306c4af3288dc00cb6f53f277a59b08209d2496 100644 (file)
@@ -799,6 +799,32 @@ class component_converter(CConverter):
 [python start generated code]*/
 /*[python end generated code: output=da39a3ee5e6b4b0d input=38e9be01d33927fb]*/
 
+static int
+attr_converter(PyObject *arg, void *ptr)
+{
+    /* attr_t is unsigned and at least as wide as chtype, so an attribute
+       value must be a non-negative integer that fits in attr_t. */
+    unsigned long attr = PyLong_AsUnsignedLong(arg);
+    if (attr == (unsigned long)-1 && PyErr_Occurred()) {
+        return 0;
+    }
+    if (attr > (unsigned long)(attr_t)-1) {
+        PyErr_Format(PyExc_OverflowError,
+                     "attribute value is greater than maximum (%lu)",
+                     (unsigned long)(attr_t)-1);
+        return 0;
+    }
+    *(attr_t *)ptr = (attr_t)attr;
+    return 1;
+}
+
+/*[python input]
+class attr_converter(CConverter):
+    type = 'attr_t'
+    converter = 'attr_converter'
+[python start generated code]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=6132d3d99d3ec25a]*/
+
 /*****************************************************************************
  The Window Object
 ******************************************************************************/
@@ -1450,6 +1476,125 @@ _curses_window_attrset_impl(PyCursesWindowObject *self, long attr)
     return curses_window_check_err(self, rtn, "wattrset", "attrset");
 }
 
+/*[clinic input]
+_curses.window.attr_get
+
+Return the window's attributes and color pair as (attrs, pair).
+[clinic start generated code]*/
+
+static PyObject *
+_curses_window_attr_get_impl(PyCursesWindowObject *self)
+/*[clinic end generated code: output=74b3f805a2958fb8 input=1efd3c450a1373ef]*/
+{
+    attr_t attrs;
+    int rtn;
+#if _NCURSES_EXTENDED_COLOR_FUNCS
+    int pair;
+    short legacy_pair;
+    rtn = wattr_get(self->win, &attrs, &legacy_pair, &pair);
+#else
+    short pair;
+    rtn = wattr_get(self->win, &attrs, &pair, NULL);
+#endif
+    if (curses_window_check_err(self, rtn, "wattr_get", "attr_get") == NULL) {
+        return NULL;
+    }
+    return Py_BuildValue("(ki)", (unsigned long)attrs, (int)pair);
+}
+
+/*[clinic input]
+_curses.window.attr_set
+
+    attr: attr
+    pair: pair = 0
+    /
+
+Set the window's attributes and color pair.
+[clinic start generated code]*/
+
+static PyObject *
+_curses_window_attr_set_impl(PyCursesWindowObject *self, attr_t attr,
+                             int pair)
+/*[clinic end generated code: output=756416e0d6345d4a input=b7936bd6b73eb3f2]*/
+{
+    int rtn;
+#if _NCURSES_EXTENDED_COLOR_FUNCS
+    rtn = wattr_set(self->win, attr, 0, &pair);
+#else
+    rtn = wattr_set(self->win, attr, (short)pair, NULL);
+#endif
+    return curses_window_check_err(self, rtn, "wattr_set", "attr_set");
+}
+
+/*[clinic input]
+_curses.window.attr_on
+
+    attr: attr
+    /
+
+Turn on the given attributes without affecting any others.
+[clinic start generated code]*/
+
+static PyObject *
+_curses_window_attr_on_impl(PyCursesWindowObject *self, attr_t attr)
+/*[clinic end generated code: output=712f13a558c5a6cb input=6a51a3d597ddca4b]*/
+{
+    int rtn = wattr_on(self->win, attr, NULL);
+    return curses_window_check_err(self, rtn, "wattr_on", "attr_on");
+}
+
+/*[clinic input]
+_curses.window.attr_off
+
+    attr: attr
+    /
+
+Turn off the given attributes without affecting any others.
+[clinic start generated code]*/
+
+static PyObject *
+_curses_window_attr_off_impl(PyCursesWindowObject *self, attr_t attr)
+/*[clinic end generated code: output=ac680aead16f74fa input=c5d778b84030d388]*/
+{
+    int rtn = wattr_off(self->win, attr, NULL);
+    return curses_window_check_err(self, rtn, "wattr_off", "attr_off");
+}
+
+/*[clinic input]
+_curses.window.color_set
+
+    pair: pair
+    /
+
+Set the window's color pair attribute.
+[clinic start generated code]*/
+
+static PyObject *
+_curses_window_color_set_impl(PyCursesWindowObject *self, int pair)
+/*[clinic end generated code: output=5e9e83f02a29bf1c input=70026f6d411db130]*/
+{
+    int rtn;
+#if _NCURSES_EXTENDED_COLOR_FUNCS
+    rtn = wcolor_set(self->win, 0, &pair);
+#else
+    rtn = wcolor_set(self->win, (short)pair, NULL);
+#endif
+    return curses_window_check_err(self, rtn, "wcolor_set", "color_set");
+}
+
+/*[clinic input]
+_curses.window.getattrs
+
+Return the window's current attributes.
+[clinic start generated code]*/
+
+static PyObject *
+_curses_window_getattrs_impl(PyCursesWindowObject *self)
+/*[clinic end generated code: output=835f499205204ec4 input=bf56a0af5b730bd1]*/
+{
+    return PyLong_FromUnsignedLong((unsigned long)(attr_t)getattrs(self->win));
+}
+
 /*[clinic input]
 _curses.window.bkgdset
 
@@ -1697,30 +1842,25 @@ PyCursesWindow_ChgAt(PyObject *op, PyObject *args)
     int num = -1;
     short color;
     attr_t attr = A_NORMAL;
-    long lattr;
     int use_xy = FALSE;
 
     switch (PyTuple_Size(args)) {
     case 1:
-        if (!PyArg_ParseTuple(args,"l;attr", &lattr))
+        if (!PyArg_ParseTuple(args,"O&;attr", attr_converter, &attr))
             return NULL;
-        attr = lattr;
         break;
     case 2:
-        if (!PyArg_ParseTuple(args,"il;n,attr", &num, &lattr))
+        if (!PyArg_ParseTuple(args,"iO&;n,attr", &num, attr_converter, &attr))
             return NULL;
-        attr = lattr;
         break;
     case 3:
-        if (!PyArg_ParseTuple(args,"iil;y,x,attr", &y, &x, &lattr))
+        if (!PyArg_ParseTuple(args,"iiO&;y,x,attr", &y, &x, attr_converter, &attr))
             return NULL;
-        attr = lattr;
         use_xy = TRUE;
         break;
     case 4:
-        if (!PyArg_ParseTuple(args,"iiil;y,x,n,attr", &y, &x, &num, &lattr))
+        if (!PyArg_ParseTuple(args,"iiiO&;y,x,n,attr", &y, &x, &num, attr_converter, &attr))
             return NULL;
-        attr = lattr;
         use_xy = TRUE;
         break;
     default:
@@ -3377,6 +3517,12 @@ static PyMethodDef PyCursesWindow_methods[] = {
     _CURSES_WINDOW_ATTROFF_METHODDEF
     _CURSES_WINDOW_ATTRON_METHODDEF
     _CURSES_WINDOW_ATTRSET_METHODDEF
+    _CURSES_WINDOW_ATTR_GET_METHODDEF
+    _CURSES_WINDOW_ATTR_SET_METHODDEF
+    _CURSES_WINDOW_ATTR_ON_METHODDEF
+    _CURSES_WINDOW_ATTR_OFF_METHODDEF
+    _CURSES_WINDOW_COLOR_SET_METHODDEF
+    _CURSES_WINDOW_GETATTRS_METHODDEF
     _CURSES_WINDOW_BKGD_METHODDEF
 #ifdef HAVE_CURSES_WCHGAT
     {
@@ -6882,6 +7028,65 @@ cursesmodule_exec(PyObject *module)
     SetDictInt("A_ITALIC",          A_ITALIC);
 #endif
 
+    /* The WA_* attributes are used by the attr_t-based functions
+       (attr_get, attr_set, ...).  ncurses defines them bit-identically to the
+       matching A_* constants, but X/Open keeps the two sets distinct, so other
+       implementations (such as NetBSD curses) may give them different values. */
+#ifdef WA_ATTRIBUTES
+    SetDictInt("WA_ATTRIBUTES",     WA_ATTRIBUTES);
+#endif
+#ifdef WA_NORMAL
+    SetDictInt("WA_NORMAL",         WA_NORMAL);
+#endif
+#ifdef WA_STANDOUT
+    SetDictInt("WA_STANDOUT",       WA_STANDOUT);
+#endif
+#ifdef WA_UNDERLINE
+    SetDictInt("WA_UNDERLINE",      WA_UNDERLINE);
+#endif
+#ifdef WA_REVERSE
+    SetDictInt("WA_REVERSE",        WA_REVERSE);
+#endif
+#ifdef WA_BLINK
+    SetDictInt("WA_BLINK",          WA_BLINK);
+#endif
+#ifdef WA_DIM
+    SetDictInt("WA_DIM",            WA_DIM);
+#endif
+#ifdef WA_BOLD
+    SetDictInt("WA_BOLD",           WA_BOLD);
+#endif
+#ifdef WA_ALTCHARSET
+    SetDictInt("WA_ALTCHARSET",     WA_ALTCHARSET);
+#endif
+#ifdef WA_INVIS
+    SetDictInt("WA_INVIS",          WA_INVIS);
+#endif
+#ifdef WA_PROTECT
+    SetDictInt("WA_PROTECT",        WA_PROTECT);
+#endif
+#ifdef WA_HORIZONTAL
+    SetDictInt("WA_HORIZONTAL",     WA_HORIZONTAL);
+#endif
+#ifdef WA_LEFT
+    SetDictInt("WA_LEFT",           WA_LEFT);
+#endif
+#ifdef WA_LOW
+    SetDictInt("WA_LOW",            WA_LOW);
+#endif
+#ifdef WA_RIGHT
+    SetDictInt("WA_RIGHT",          WA_RIGHT);
+#endif
+#ifdef WA_TOP
+    SetDictInt("WA_TOP",            WA_TOP);
+#endif
+#ifdef WA_VERTICAL
+    SetDictInt("WA_VERTICAL",       WA_VERTICAL);
+#endif
+#ifdef WA_ITALIC
+    SetDictInt("WA_ITALIC",         WA_ITALIC);
+#endif
+
     SetDictInt("COLOR_BLACK",       COLOR_BLACK);
     SetDictInt("COLOR_RED",         COLOR_RED);
     SetDictInt("COLOR_GREEN",       COLOR_GREEN);
index 49f30a35656b4851fe43a898c14669fe408a1149..d4d6e4eeef01584df1168e796f2445071624787d 100644 (file)
@@ -353,6 +353,162 @@ exit:
     return return_value;
 }
 
+PyDoc_STRVAR(_curses_window_attr_get__doc__,
+"attr_get($self, /)\n"
+"--\n"
+"\n"
+"Return the window\'s attributes and color pair as (attrs, pair).");
+
+#define _CURSES_WINDOW_ATTR_GET_METHODDEF    \
+    {"attr_get", (PyCFunction)_curses_window_attr_get, METH_NOARGS, _curses_window_attr_get__doc__},
+
+static PyObject *
+_curses_window_attr_get_impl(PyCursesWindowObject *self);
+
+static PyObject *
+_curses_window_attr_get(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    return _curses_window_attr_get_impl((PyCursesWindowObject *)self);
+}
+
+PyDoc_STRVAR(_curses_window_attr_set__doc__,
+"attr_set($self, attr, pair=0, /)\n"
+"--\n"
+"\n"
+"Set the window\'s attributes and color pair.");
+
+#define _CURSES_WINDOW_ATTR_SET_METHODDEF    \
+    {"attr_set", _PyCFunction_CAST(_curses_window_attr_set), METH_FASTCALL, _curses_window_attr_set__doc__},
+
+static PyObject *
+_curses_window_attr_set_impl(PyCursesWindowObject *self, attr_t attr,
+                             int pair);
+
+static PyObject *
+_curses_window_attr_set(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
+{
+    PyObject *return_value = NULL;
+    attr_t attr;
+    int pair = 0;
+
+    if (!_PyArg_CheckPositional("attr_set", nargs, 1, 2)) {
+        goto exit;
+    }
+    if (!attr_converter(args[0], &attr)) {
+        goto exit;
+    }
+    if (nargs < 2) {
+        goto skip_optional;
+    }
+    if (!pair_converter(args[1], &pair)) {
+        goto exit;
+    }
+skip_optional:
+    return_value = _curses_window_attr_set_impl((PyCursesWindowObject *)self, attr, pair);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_curses_window_attr_on__doc__,
+"attr_on($self, attr, /)\n"
+"--\n"
+"\n"
+"Turn on the given attributes without affecting any others.");
+
+#define _CURSES_WINDOW_ATTR_ON_METHODDEF    \
+    {"attr_on", (PyCFunction)_curses_window_attr_on, METH_O, _curses_window_attr_on__doc__},
+
+static PyObject *
+_curses_window_attr_on_impl(PyCursesWindowObject *self, attr_t attr);
+
+static PyObject *
+_curses_window_attr_on(PyObject *self, PyObject *arg)
+{
+    PyObject *return_value = NULL;
+    attr_t attr;
+
+    if (!attr_converter(arg, &attr)) {
+        goto exit;
+    }
+    return_value = _curses_window_attr_on_impl((PyCursesWindowObject *)self, attr);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_curses_window_attr_off__doc__,
+"attr_off($self, attr, /)\n"
+"--\n"
+"\n"
+"Turn off the given attributes without affecting any others.");
+
+#define _CURSES_WINDOW_ATTR_OFF_METHODDEF    \
+    {"attr_off", (PyCFunction)_curses_window_attr_off, METH_O, _curses_window_attr_off__doc__},
+
+static PyObject *
+_curses_window_attr_off_impl(PyCursesWindowObject *self, attr_t attr);
+
+static PyObject *
+_curses_window_attr_off(PyObject *self, PyObject *arg)
+{
+    PyObject *return_value = NULL;
+    attr_t attr;
+
+    if (!attr_converter(arg, &attr)) {
+        goto exit;
+    }
+    return_value = _curses_window_attr_off_impl((PyCursesWindowObject *)self, attr);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_curses_window_color_set__doc__,
+"color_set($self, pair, /)\n"
+"--\n"
+"\n"
+"Set the window\'s color pair attribute.");
+
+#define _CURSES_WINDOW_COLOR_SET_METHODDEF    \
+    {"color_set", (PyCFunction)_curses_window_color_set, METH_O, _curses_window_color_set__doc__},
+
+static PyObject *
+_curses_window_color_set_impl(PyCursesWindowObject *self, int pair);
+
+static PyObject *
+_curses_window_color_set(PyObject *self, PyObject *arg)
+{
+    PyObject *return_value = NULL;
+    int pair;
+
+    if (!pair_converter(arg, &pair)) {
+        goto exit;
+    }
+    return_value = _curses_window_color_set_impl((PyCursesWindowObject *)self, pair);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_curses_window_getattrs__doc__,
+"getattrs($self, /)\n"
+"--\n"
+"\n"
+"Return the window\'s current attributes.");
+
+#define _CURSES_WINDOW_GETATTRS_METHODDEF    \
+    {"getattrs", (PyCFunction)_curses_window_getattrs, METH_NOARGS, _curses_window_getattrs__doc__},
+
+static PyObject *
+_curses_window_getattrs_impl(PyCursesWindowObject *self);
+
+static PyObject *
+_curses_window_getattrs(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    return _curses_window_getattrs_impl((PyCursesWindowObject *)self);
+}
+
 PyDoc_STRVAR(_curses_window_bkgdset__doc__,
 "bkgdset($self, ch, attr=_curses.A_NORMAL, /)\n"
 "--\n"
@@ -4966,4 +5122,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=fd0f4e65dc594a65 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=3d8d59f44ded2226 input=a9049054013a1b77]*/