]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-151757: Support wide and combining characters in the curses module (GH-151758)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 24 Jun 2026 05:52:10 +0000 (08:52 +0300)
committerGitHub <noreply@github.com>
Wed, 24 Jun 2026 05:52:10 +0000 (08:52 +0300)
The character-cell window methods now accept a full character cell -- a
spacing character optionally followed by combining characters (up to
CCHARW_MAX wide characters) -- in addition to a single int or byte
character.  This affects addch(), bkgd(), bkgdset(), border(), box(),
echochar(), hline(), insch() and vline(); they dispatch to the ncursesw
wide-character functions (wadd_wch(), wbkgrnd(), wborder_set(),
wecho_wchar(), whline_set(), wins_wch(), wvline_set(), ...) when given a
string.  border() and box() cannot mix integer or byte characters with
wide string characters in a single call.  A cell is one spacing character
optionally followed by combining characters, so an extra spacing or
control character (such as "ab") is rejected with ValueError rather than
being silently truncated by setcchar().

Also add the wide-character read methods get_wstr() and in_wstr(), the
counterparts of getstr() and instr() that return a str rather than a
bytes object, and the module functions erasewchar(), killwchar() and
wunctrl(), the wide-character counterparts of erasechar(), killchar()
and unctrl().

All of this is available only when built against the wide-character
ncursesw library.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Doc/library/curses.rst
Doc/whatsnew/3.16.rst
Lib/test/test_curses.py
Misc/NEWS.d/next/Library/2026-06-20-02-14-55.gh-issue-151757.TP9A2x.rst [new file with mode: 0644]
Modules/_cursesmodule.c
Modules/clinic/_cursesmodule.c.h

index d7873054d6b9154073a9a6fd95e29c3edb2be66e..3c9a27aa85e6a0913182e958a3d661b4064fef33 100644 (file)
@@ -194,6 +194,16 @@ The module :mod:`!curses` defines the following functions:
    the curses library itself.
 
 
+.. function:: erasewchar()
+
+   Return the user's current erase character as a one-character string.
+   This is the wide-character variant of :func:`erasechar`.  Availability
+   depends on building Python against a wide-character-aware version of the
+   underlying curses library.
+
+   .. versionadded:: next
+
+
 .. function:: filter()
 
    The :func:`.filter` routine, if used, must be called before :func:`initscr` is
@@ -379,6 +389,16 @@ The module :mod:`!curses` defines the following functions:
    by the curses library itself.
 
 
+.. function:: killwchar()
+
+   Return the user's current line kill character as a one-character string.
+   This is the wide-character variant of :func:`killchar`.  Availability
+   depends on building Python against a wide-character-aware version of the
+   underlying curses library.
+
+   .. versionadded:: next
+
+
 .. function:: longname()
 
    Return a bytes object containing the terminfo long name field describing the current
@@ -690,6 +710,18 @@ The module :mod:`!curses` defines the following functions:
    example as ``b'^C'``. Printing characters are left as they are.
 
 
+.. function:: wunctrl(ch)
+
+   Return a string which is a printable representation of the wide character *ch*.
+   Control characters are represented as a caret followed by the character, for
+   example as ``'^C'``.  Printing characters are left as they are.  This is the
+   wide-character variant of :func:`unctrl`, returning a :class:`str` rather than
+   :class:`bytes`.  Availability depends on building Python against a
+   wide-character-aware version of the underlying curses library.
+
+   .. versionadded:: next
+
+
 .. function:: ungetch(ch)
 
    Push *ch* so the next :meth:`~window.getch` will return it.
@@ -770,12 +802,19 @@ Window objects
    character previously painted at that location.  By default, the character
    position and attributes are the current settings for the window object.
 
+   *ch* may be a single character, optionally followed by combining
+   characters, that together occupy one character cell.
+
    .. note::
 
       Writing outside the window, subwindow, or pad raises a :exc:`curses.error`.
       Attempting to write to the lower-right corner of a window, subwindow,
       or pad will cause an exception to be raised after the character is printed.
 
+   .. versionchanged:: next
+      A character may now be given as a string of a base character followed
+      by combining characters, instead of only a single character.
+
 
 .. method:: window.addnstr(str, n[, attr])
             window.addnstr(y, x, str, n[, attr])
@@ -834,6 +873,9 @@ Window objects
    * Wherever  the  former background character appears, it is changed to the new
      background character.
 
+   .. versionchanged:: next
+      Wide and combining characters are now accepted.
+
 
 .. method:: window.bkgdset(ch[, attr])
 
@@ -844,6 +886,9 @@ Window objects
    characters.  The background becomes a property of the character and moves with
    the character through any scrolling and insert/delete line/character operations.
 
+   .. versionchanged:: next
+      Wide and combining characters are now accepted.
+
 
 .. method:: window.border([ls[, rs[, ts[, bs[, tl[, tr[, bl[, br]]]]]]]])
 
@@ -877,12 +922,20 @@ Window objects
    | *br*      | Bottom-right corner | :const:`ACS_LRCORNER` |
    +-----------+---------------------+-----------------------+
 
+   .. versionchanged:: next
+      Wide and combining characters are now accepted.  A single call cannot mix
+      them with integer or byte characters.
+
 
 .. method:: window.box([vertch, horch])
 
    Similar to :meth:`border`, but both *ls* and *rs* are *vertch* and both *ts* and
    *bs* are *horch*.  The default corner characters are always used by this function.
 
+   .. versionchanged:: next
+      Wide and combining characters are now accepted.  A single call cannot mix
+      them with integer or byte characters.
+
 
 .. method:: window.chgat(attr)
             window.chgat(num, attr)
@@ -951,6 +1004,9 @@ Window objects
    Add character *ch* with attribute *attr*, and immediately  call :meth:`refresh`
    on the window.
 
+   .. versionchanged:: next
+      Wide and combining characters are now accepted.
+
 
 .. method:: window.enclose(y, x)
 
@@ -1038,6 +1094,20 @@ Window objects
       The maximum value for *n* was increased from 1023 to 2047.
 
 
+.. method:: window.get_wstr()
+            window.get_wstr(n)
+            window.get_wstr(y, x)
+            window.get_wstr(y, x, n)
+
+   Read a string from the user, with primitive line editing capacity.
+   This is the wide-character variant of :meth:`getstr`: it returns a
+   :class:`str` rather than a :class:`bytes` object, so it can return
+   characters that are not representable in the window's encoding.
+   At most *n* characters are read; *n* defaults to and cannot exceed 2047.
+
+   .. versionadded:: next
+
+
 .. method:: window.getyx()
 
    Return a tuple ``(y, x)`` of current cursor position  relative to the window's
@@ -1051,6 +1121,9 @@ Window objects
    the character *ch* with attributes *attr*.  The line stops at the right edge
    of the window if fewer than *n* cells are available.
 
+   .. versionchanged:: next
+      Wide and combining characters are now accepted.
+
 
 .. method:: window.idcok(flag)
 
@@ -1088,6 +1161,9 @@ Window objects
    cursor are shifted one position right, with the rightmost character on the
    line being lost.  The cursor position does not change.
 
+   .. versionchanged:: next
+      Wide and combining characters are now accepted.
+
 
 .. method:: window.insdelln(nlines)
 
@@ -1137,6 +1213,19 @@ Window objects
       The maximum value for *n* was increased from 1023 to 2047.
 
 
+.. method:: window.in_wstr([n])
+            window.in_wstr(y, x[, n])
+
+   Return a string of characters, extracted from the window starting at the
+   current cursor position, or at *y*, *x* if specified.  This is the
+   wide-character variant of :meth:`instr`: it returns a :class:`str` rather
+   than a :class:`bytes` object, so it can return characters that are not
+   representable in the window's encoding.  Attributes and color information
+   are stripped from the characters.  The maximum value for *n* is 2047.
+
+   .. versionadded:: next
+
+
 .. method:: window.is_linetouched(line)
 
    Return ``True`` if the specified line was modified since the last call to
@@ -1386,6 +1475,9 @@ Window objects
    Display a vertical line starting at ``(y, x)`` with length *n* consisting of the
    character *ch* with attributes *attr*.
 
+   .. versionchanged:: next
+      Wide and combining characters are now accepted.
+
 
 Constants
 ---------
index 9242a1ae3ea6ba1f00bdf518f62dc12b9ddcafab..e3f04739e3b49d56f77238934a8eb37dd315c94c 100644 (file)
@@ -89,6 +89,25 @@ Improved modules
 curses
 ------
 
+* The :mod:`curses` character-cell window methods now accept a full character
+  cell --- a spacing character optionally followed by combining characters ---
+  in addition to a single integer or byte character.  This affects
+  :meth:`~curses.window.addch`, :meth:`~curses.window.bkgd`,
+  :meth:`~curses.window.bkgdset`, :meth:`~curses.window.border`,
+  :meth:`~curses.window.box`, :meth:`~curses.window.echochar`,
+  :meth:`~curses.window.hline`, :meth:`~curses.window.insch` and
+  :meth:`~curses.window.vline`.
+  Also add the wide-character read methods :meth:`~curses.window.get_wstr` and
+  :meth:`~curses.window.in_wstr`, the counterparts of
+  :meth:`~curses.window.getstr` and :meth:`~curses.window.instr` that return a
+  :class:`str` rather than :class:`bytes`,
+  and the module functions :func:`curses.erasewchar`, :func:`curses.killwchar`
+  and :func:`curses.wunctrl`, the wide-character counterparts of
+  :func:`curses.erasechar`, :func:`curses.killchar` and :func:`curses.unctrl`.
+  These features are only available when built against the wide-character
+  ncursesw library.
+  (Contributed by Serhiy Storchaka in :gh:`151757`.)
+
 * Add :func:`curses.nofilter`, which undoes the effect of :func:`curses.filter`.
   (Contributed by Serhiy Storchaka in :gh:`151744`.)
 
index 98f1a7c8a0a2c5c060692fbedd446c51a0c53233..3427883dc0ffa2a56bf5a5cc23246d284c4ced10 100644 (file)
@@ -253,6 +253,69 @@ class TestCurses(unittest.TestCase):
                 self.assertIs(win.is_wintouched(), syncok)
                 self.assertIs(stdscr.is_wintouched(), syncok)
 
+    @requires_curses_window_meth('get_wch')
+    def test_addch_combining(self):
+        # A character cell may hold a spacing char plus combining marks.
+        stdscr = self.stdscr
+        stdscr.move(0, 0)
+        stdscr.addch('e\u0301')              # 'e' + COMBINING ACUTE ACCENT
+        stdscr.addch(1, 0, 'a\u0323\u0300')  # base plus two combining marks
+        # Too many code points to fit in a single character cell.
+        self.assertRaises(TypeError, stdscr.addch, 'e' + '\u0301' * 10)
+        # Only the first code point may be a spacing character.
+        self.assertRaises(ValueError, stdscr.addch, 'ab')
+        self.assertRaises(ValueError, stdscr.addch, 'a\u0301b')
+        # A lone control character is allowed (like addch(ord('\n'))), but it
+        # cannot be combined with other characters, as base or otherwise.
+        stdscr.addch('\n')
+        self.assertRaises(ValueError, stdscr.addch, 'a\n')
+        self.assertRaises(ValueError, stdscr.addch, '\n\u0301')
+        self.assertRaises(ValueError, stdscr.addch, '\ne\u0301')
+
+    @requires_curses_window_meth('get_wch')
+    def test_addch_emoji(self):
+        # curses has no grapheme-cluster support: a cell holds one spacing
+        # character plus zero-width combining characters.  A lone emoji fits,
+        # as does an emoji with a zero-width variation selector.
+        stdscr = self.stdscr
+        stdscr.addch(0, 0, '\U0001f600')          # single emoji
+        stdscr.addch(1, 0, '\u263a\ufe0f')        # WHITE SMILING FACE + VS-16
+        # An emoji ZWJ sequence or an emoji with a modifier is more than one
+        # spacing character and cannot share a single cell.
+        self.assertRaises(ValueError, stdscr.addch,
+                          '\U0001f44d\U0001f3fd')          # thumbs up + skin tone
+        self.assertRaises(ValueError, stdscr.addch,
+                          '\U0001f468\u200d\U0001f469')    # man ZWJ woman
+
+    @requires_curses_window_meth('get_wch')
+    def test_wide_characters(self):
+        # Wide and combining characters in the character-cell methods.
+        stdscr = self.stdscr
+        combining = 'e\u0301'              # 'e' + COMBINING ACUTE ACCENT
+        vline, hline = '\u2502', '\u2500'  # box-drawing vertical/horizontal
+        stdscr.move(0, 0)
+        stdscr.echochar(combining)
+        stdscr.insch(1, 0, combining)
+        stdscr.hline(2, 0, hline, 5)
+        stdscr.vline(3, 0, vline, 3)
+        stdscr.bkgdset(combining)
+        stdscr.bkgd(combining)
+        stdscr.border(vline, vline, hline, hline)
+        stdscr.box(vline, hline)
+        # border() and box() cannot mix integer and wide-string characters.
+        self.assertRaises(TypeError, stdscr.box, vline, ord('-'))
+
+
+    @requires_curses_window_meth('in_wstr')
+    def test_in_wstr(self):
+        # The wide-character window read returns a str (instr returns bytes).
+        stdscr = self.stdscr
+        s = 'a\u00e9\u2502z'  # 'a', 'e'+acute (precomposed), box vline, 'z'
+        stdscr.addstr(0, 0, s)
+        self.assertEqual(stdscr.in_wstr(0, 0, len(s)), s)
+        self.assertIsInstance(stdscr.instr(0, 0, len(s)), bytes)
+
+
     def test_output_character(self):
         stdscr = self.stdscr
         encoding = stdscr.encoding
@@ -281,13 +344,16 @@ class TestCurses(unittest.TestCase):
         stdscr.echochar('A')
         stdscr.echochar(b'A')
         stdscr.echochar(65)
-        with self.assertRaises((UnicodeEncodeError, OverflowError)):
-            # Unicode is not fully supported yet, but at least it does
-            # not crash.
-            # It is supposed to fail because either the character is
-            # not encodable with the current encoding, or it is encoded to
-            # a multibyte sequence.
-            stdscr.echochar('\u0114')
+        c = '\u0114'
+        try:
+            stdscr.echochar(c)
+        except UnicodeEncodeError:
+            # The character is not encodable with the current encoding.
+            self.assertRaises(UnicodeEncodeError, c.encode, encoding)
+        except OverflowError:
+            # The character is encoded to a multibyte sequence.
+            encoded = c.encode(encoding)
+            self.assertNotEqual(len(encoded), 1, repr(encoded))
         stdscr.echochar('A', curses.A_BOLD)
         self.assertIs(stdscr.is_wintouched(), False)
 
@@ -742,7 +808,6 @@ class TestCurses(unittest.TestCase):
         self.assertEqual(win.inch(3, 1), b'a'[0])
 
     def test_unctrl(self):
-        # TODO: wunctrl()
         self.assertEqual(curses.unctrl(b'A'), b'A')
         self.assertEqual(curses.unctrl('A'), b'A')
         self.assertEqual(curses.unctrl(65), b'A')
@@ -753,6 +818,21 @@ class TestCurses(unittest.TestCase):
         self.assertRaises(TypeError, curses.unctrl, b'AB')
         self.assertRaises(TypeError, curses.unctrl, '')
         self.assertRaises(TypeError, curses.unctrl, 'AB')
+
+    @requires_curses_func('wunctrl')
+    def test_wunctrl(self):
+        # The wide-character variant of unctrl() returns a str.
+        self.assertEqual(curses.wunctrl(b'A'), 'A')
+        self.assertEqual(curses.wunctrl('A'), 'A')
+        self.assertEqual(curses.wunctrl(65), 'A')
+        self.assertEqual(curses.wunctrl('\n'), '^J')
+        self.assertEqual(curses.wunctrl(10), '^J')
+        self.assertEqual(curses.wunctrl('é'), 'é')  # printable
+        self.assertRaises(TypeError, curses.wunctrl, b'')
+        self.assertRaises(TypeError, curses.wunctrl, b'AB')
+        self.assertRaises(TypeError, curses.wunctrl, '')
+        # More than one spacing character is not a single cell.
+        self.assertRaises(ValueError, curses.wunctrl, 'AB')
         self.assertRaises(OverflowError, curses.unctrl, 2**64)
 
     def test_endwin(self):
@@ -800,7 +880,7 @@ class TestCurses(unittest.TestCase):
         curses.newpad(50, 50)
 
     def test_env_queries(self):
-        # TODO: term_attrs(), erasewchar(), killwchar()
+        # TODO: term_attrs()
         self.assertIsInstance(curses.termname(), bytes)
         self.assertIsInstance(curses.longname(), bytes)
         self.assertIsInstance(curses.baudrate(), int)
@@ -815,6 +895,24 @@ class TestCurses(unittest.TestCase):
         self.assertIsInstance(c, bytes)
         self.assertEqual(len(c), 1)
 
+        # The erase and kill characters are a property of the controlling
+        # terminal: the wide variants report ERR (raising curses.error) without
+        # one, while the narrow variants above return an unspecified byte.
+        try:
+            tty_fd = os.open(os.ctermid(), os.O_RDONLY)
+        except OSError:
+            tty_fd = None
+        if tty_fd is not None:
+            os.close(tty_fd)
+            if hasattr(curses, 'erasewchar'):
+                c = curses.erasewchar()
+                self.assertIsInstance(c, str)
+                self.assertEqual(len(c), 1)
+            if hasattr(curses, 'killwchar'):
+                c = curses.killwchar()
+                self.assertIsInstance(c, str)
+                self.assertEqual(len(c), 1)
+
     def test_output_options(self):
         stdscr = self.stdscr
 
diff --git a/Misc/NEWS.d/next/Library/2026-06-20-02-14-55.gh-issue-151757.TP9A2x.rst b/Misc/NEWS.d/next/Library/2026-06-20-02-14-55.gh-issue-151757.TP9A2x.rst
new file mode 100644 (file)
index 0000000..34db969
--- /dev/null
@@ -0,0 +1,7 @@
+The :mod:`curses` character-cell window methods now accept a full character
+cell -- a spacing character optionally followed by combining characters -- in
+addition to a single integer or byte character.  Add the wide-character read
+methods :meth:`curses.window.get_wstr` and :meth:`curses.window.in_wstr`, and
+the functions :func:`curses.erasewchar`, :func:`curses.killwchar` and
+:func:`curses.wunctrl`.  These features are only available when built against
+the wide-character ncursesw library.
index e60cba3ef87ead1c353bf70bb8bd3dbe8fe30333..f0541b82ae070d7b6d5fedc4896e3accd6a8f053 100644 (file)
@@ -500,7 +500,8 @@ overflow:
 
     - int
     - bytes of length 1
-    - str of length 1
+    - str of length 1, or a spacing character followed by up to
+      CCHARW_MAX - 1 combining characters
 
    Return:
 
@@ -516,20 +517,43 @@ PyCurses_ConvertToCchar_t(PyCursesWindowObject *win, PyObject *obj,
                           )
 {
     long value;
-#ifdef HAVE_NCURSESW
-    wchar_t buffer[2];
-#endif
 
     if (PyUnicode_Check(obj)) {
 #ifdef HAVE_NCURSESW
-        if (PyUnicode_AsWideChar(obj, buffer, 2) != 1) {
+        /* A character cell may hold a spacing character plus up to
+           CCHARW_MAX - 1 combining characters; wch must point to a buffer
+           of at least CCHARW_MAX + 1 wide characters. */
+        Py_ssize_t nch = PyUnicode_AsWideChar(obj, wch, CCHARW_MAX + 1);
+        if (nch < 0) {
+            return 0;
+        }
+        if (nch == 0 || nch > CCHARW_MAX) {
             PyErr_Format(PyExc_TypeError,
-                         "expect int or bytes or str of length 1, "
-                         "got a str of length %zi",
-                         PyUnicode_GET_LENGTH(obj));
+                         "expect int or bytes or a string of 1 to %d "
+                         "characters, got a str of length %zi",
+                         (int)CCHARW_MAX, PyUnicode_GET_LENGTH(obj));
             return 0;
         }
-        *wch = buffer[0];
+        /* A character cell is a single spacing character optionally followed
+           by combining characters.  A lone control character is still allowed
+           (like addch(ord('\n'))), but in a multi-character cell the base must
+           be a printable character and the rest must be zero-width combining
+           characters.  Validate this explicitly: otherwise setcchar() would
+           silently drop a trailing spacing character, or fail with a generic
+           error for a control character used as the base. */
+        if (nch > 1) {
+            int bad = wcwidth(wch[0]) < 0;
+            for (Py_ssize_t i = 1; !bad && i < nch; i++) {
+                bad = wcwidth(wch[i]) != 0;
+            }
+            if (bad) {
+                PyErr_SetString(PyExc_ValueError,
+                                "a character cell must be a single spacing "
+                                "character optionally followed by combining "
+                                "characters");
+                return 0;
+            }
+        }
         return 2;
 #else
         return PyCurses_ConvertToChtype(win, obj, ch);
@@ -617,6 +641,33 @@ PyCurses_ConvertToString(PyCursesWindowObject *win, PyObject *obj,
     return 0;
 }
 
+#ifdef HAVE_NCURSESW
+/* Build a single character cell from obj.
+
+   On success return 1 and store the raw chtype (without *attr*) in *pch when
+   obj is an int or bytes, or return 2 and store a cchar_t (with *attr*
+   applied) in *pwc when obj is a str -- a spacing character optionally
+   followed by combining characters.  Return 0 and set an exception on error.
+
+   This lets a method use the wide *_set functions (which accept combining
+   characters) for string arguments while still accepting integer chtype
+   values. */
+static int
+PyCurses_ConvertToCell(PyCursesWindowObject *win, PyObject *obj, long attr,
+                       const char *funcname, chtype *pch, cchar_t *pwc)
+{
+    wchar_t wstr[CCHARW_MAX + 1];
+    int type = PyCurses_ConvertToCchar_t(win, obj, pch, wstr);
+    if (type == 2) {
+        if (setcchar(pwc, wstr, (attr_t)attr, PAIR_NUMBER(attr), NULL) == ERR) {
+            curses_window_set_error(win, "setcchar", funcname);
+            return 0;
+        }
+    }
+    return type;
+}
+#endif
+
 static int
 color_allow_default_converter(PyObject *arg, void *ptr)
 {
@@ -997,7 +1048,7 @@ _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1,
     int type;
     chtype cch = 0;
 #ifdef HAVE_NCURSESW
-    wchar_t wstr[2];
+    wchar_t wstr[CCHARW_MAX + 1];
     cchar_t wcval;
 #endif
     const char *funcname;
@@ -1005,7 +1056,6 @@ _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1,
 #ifdef HAVE_NCURSESW
     type = PyCurses_ConvertToCchar_t(self, ch, &cch, wstr);
     if (type == 2) {
-        wstr[1] = L'\0';
         rtn = setcchar(&wcval, wstr, attr, PAIR_NUMBER(attr), NULL);
         if (rtn == ERR) {
             curses_window_set_error(self, "setcchar", "addch");
@@ -1277,11 +1327,22 @@ _curses_window_bkgd_impl(PyCursesWindowObject *self, PyObject *ch, long attr)
 /*[clinic end generated code: output=058290afb2cf4034 input=634015bcb339283d]*/
 {
     chtype bkgd;
-
+#ifdef HAVE_NCURSESW
+    cchar_t wch;
+    int type = PyCurses_ConvertToCell(self, ch, attr, "bkgd", &bkgd, &wch);
+    if (type == 0) {
+        return NULL;
+    }
+    if (type == 2) {
+        int rtn = wbkgrnd(self->win, &wch);
+        return curses_window_check_err(self, rtn, "wbkgrnd", "bkgd");
+    }
+#else
     if (!PyCurses_ConvertToChtype(self, ch, &bkgd))
         return NULL;
+#endif
 
-    int rtn = wbkgd(self->win, bkgd | attr);
+    int rtn = wbkgd(self->win, bkgd | (attr_t)attr);
     return curses_window_check_err(self, rtn, "wbkgd", "bkgd");
 }
 
@@ -1354,11 +1415,22 @@ _curses_window_bkgdset_impl(PyCursesWindowObject *self, PyObject *ch,
 /*[clinic end generated code: output=8cb994fc4d7e2496 input=e09c682425c9e45b]*/
 {
     chtype bkgd;
-
+#ifdef HAVE_NCURSESW
+    cchar_t wch;
+    int type = PyCurses_ConvertToCell(self, ch, attr, "bkgdset", &bkgd, &wch);
+    if (type == 0) {
+        return NULL;
+    }
+    if (type == 2) {
+        wbkgrndset(self->win, &wch);
+        Py_RETURN_NONE;
+    }
+#else
     if (!PyCurses_ConvertToChtype(self, ch, &bkgd))
         return NULL;
+#endif
 
-    wbkgdset(self->win, bkgd | attr);
+    wbkgdset(self->win, bkgd | (attr_t)attr);
     Py_RETURN_NONE;
 }
 
@@ -1400,25 +1472,56 @@ _curses_window_border_impl(PyCursesWindowObject *self, PyObject *ls,
 {
     chtype ch[8];
     int i, rtn;
+    PyObject *objs[8] = {ls, rs, ts, bs, tl, tr, bl, br};
 
     /* Clear the array of parameters */
-    for(i=0; i<8; i++)
+    for (i = 0; i < 8; i++)
         ch[i] = 0;
 
-#define CONVERTTOCHTYPE(obj, i) \
-    if ((obj) != NULL && !PyCurses_ConvertToChtype(self, (obj), &ch[(i)])) \
-        return NULL;
-
-    CONVERTTOCHTYPE(ls, 0);
-    CONVERTTOCHTYPE(rs, 1);
-    CONVERTTOCHTYPE(ts, 2);
-    CONVERTTOCHTYPE(bs, 3);
-    CONVERTTOCHTYPE(tl, 4);
-    CONVERTTOCHTYPE(tr, 5);
-    CONVERTTOCHTYPE(bl, 6);
-    CONVERTTOCHTYPE(br, 7);
-
-#undef CONVERTTOCHTYPE
+#ifdef HAVE_NCURSESW
+    cchar_t wch[8];
+    const cchar_t *wch_p[8];
+    int use_wide = 0;
+    int types[8];
+    for (i = 0; i < 8; i++) {
+        types[i] = 0;
+        if (objs[i] != NULL) {
+            types[i] = PyCurses_ConvertToCell(self, objs[i], A_NORMAL,
+                                              "border", &ch[i], &wch[i]);
+            if (types[i] == 0) {
+                return NULL;
+            }
+            if (types[i] == 2) {
+                use_wide = 1;
+            }
+        }
+    }
+    if (use_wide) {
+        for (i = 0; i < 8; i++) {
+            if (objs[i] == NULL) {
+                wch_p[i] = NULL;  /* use the default character */
+            }
+            else if (types[i] == 2) {
+                wch_p[i] = &wch[i];
+            }
+            else {
+                PyErr_SetString(PyExc_TypeError,
+                                "border() cannot mix integer or bytes "
+                                "characters with wide string characters");
+                return NULL;
+            }
+        }
+        rtn = wborder_set(self->win,
+                          wch_p[0], wch_p[1], wch_p[2], wch_p[3],
+                          wch_p[4], wch_p[5], wch_p[6], wch_p[7]);
+        return curses_window_check_err(self, rtn, "wborder_set", "border");
+    }
+#else
+    for (i = 0; i < 8; i++) {
+        if (objs[i] != NULL && !PyCurses_ConvertToChtype(self, objs[i], &ch[i]))
+            return NULL;
+    }
+#endif
 
     rtn = wborder(self->win,
                   ch[0], ch[1], ch[2], ch[3],
@@ -1450,6 +1553,31 @@ _curses_window_box_impl(PyCursesWindowObject *self, int group_right_1,
 /*[clinic end generated code: output=f3fcb038bb287192 input=e11acb7dbf6790b6]*/
 {
     chtype ch1 = 0, ch2 = 0;
+#ifdef HAVE_NCURSESW
+    cchar_t wch1, wch2;
+    int t1 = 0, t2 = 0;
+    if (group_right_1) {
+        t1 = PyCurses_ConvertToCell(self, verch, A_NORMAL, "box", &ch1, &wch1);
+        if (t1 == 0) {
+            return NULL;
+        }
+        t2 = PyCurses_ConvertToCell(self, horch, A_NORMAL, "box", &ch2, &wch2);
+        if (t2 == 0) {
+            return NULL;
+        }
+    }
+    if (t1 == 2 || t2 == 2) {
+        if (t1 != 2 || t2 != 2) {
+            PyErr_SetString(PyExc_TypeError,
+                            "box() cannot mix integer or bytes characters "
+                            "with wide string characters");
+            return NULL;
+        }
+        int rtn = wborder_set(self->win, &wch1, &wch1, &wch2, &wch2,
+                              NULL, NULL, NULL, NULL);
+        return curses_window_check_err(self, rtn, "wborder_set", "box");
+    }
+#else
     if (group_right_1) {
         if (!PyCurses_ConvertToChtype(self, verch, &ch1)) {
             return NULL;
@@ -1458,6 +1586,7 @@ _curses_window_box_impl(PyCursesWindowObject *self, int group_right_1,
             return NULL;
         }
     }
+#endif
     return curses_window_check_err(self, box(self->win, ch1, ch2), "box", NULL);
 }
 
@@ -1661,9 +1790,32 @@ _curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch,
 /*[clinic end generated code: output=13e7dd875d4b9642 input=e7f34b964e92b156]*/
 {
     chtype ch_;
-
+#ifdef HAVE_NCURSESW
+    cchar_t wch;
+    int type = PyCurses_ConvertToCell(self, ch, attr, "echochar", &ch_, &wch);
+    if (type == 0) {
+        return NULL;
+    }
+    if (type == 2) {
+        int rtn;
+        const char *funcname;
+#ifdef py_is_pad
+        if (py_is_pad(self->win)) {
+            rtn = pecho_wchar(self->win, &wch);
+            funcname = "pecho_wchar";
+        }
+        else
+#endif
+        {
+            rtn = wecho_wchar(self->win, &wch);
+            funcname = "wecho_wchar";
+        }
+        return curses_window_check_err(self, rtn, funcname, "echochar");
+    }
+#else
     if (!PyCurses_ConvertToChtype(self, ch, &ch_))
         return NULL;
+#endif
 
     int rtn;
     const char *funcname;
@@ -2013,15 +2165,28 @@ _curses_window_hline_impl(PyCursesWindowObject *self, int group_left_1,
 /*[clinic end generated code: output=c00d489d61fc9eef input=81a4dea47268163e]*/
 {
     chtype ch_;
-
+#ifdef HAVE_NCURSESW
+    cchar_t wch;
+    int type = PyCurses_ConvertToCell(self, ch, attr, "hline", &ch_, &wch);
+    if (type == 0) {
+        return NULL;
+    }
+#else
     if (!PyCurses_ConvertToChtype(self, ch, &ch_))
         return NULL;
+#endif
     if (group_left_1) {
         if (wmove(self->win, y, x) == ERR) {
             curses_window_set_error(self, "wmove", "hline");
             return NULL;
         }
     }
+#ifdef HAVE_NCURSESW
+    if (type == 2) {
+        int rtn = whline_set(self->win, &wch, n);
+        return curses_window_check_err(self, rtn, "whline_set", "hline");
+    }
+#endif
     int rtn = whline(self->win, ch_ | (attr_t)attr, n);
     return curses_window_check_err(self, rtn, "whline", "hline");
 }
@@ -2059,11 +2224,29 @@ _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1,
 {
     int rtn;
     chtype ch_ = 0;
-
+    const char *funcname;
+#ifdef HAVE_NCURSESW
+    cchar_t wch;
+    int type = PyCurses_ConvertToCell(self, ch, attr, "insch", &ch_, &wch);
+    if (type == 0) {
+        return NULL;
+    }
+    if (type == 2) {
+        if (!group_left_1) {
+            rtn = wins_wch(self->win, &wch);
+            funcname = "wins_wch";
+        }
+        else {
+            rtn = mvwins_wch(self->win, y, x, &wch);
+            funcname = "mvwins_wch";
+        }
+        return curses_window_check_err(self, rtn, funcname, "insch");
+    }
+#else
     if (!PyCurses_ConvertToChtype(self, ch, &ch_))
         return NULL;
+#endif
 
-    const char *funcname;
     if (!group_left_1) {
         rtn = winsch(self->win, ch_ | (attr_t)attr);
         funcname = "winsch";
@@ -2169,6 +2352,125 @@ PyCursesWindow_instr(PyObject *op, PyObject *args)
     return PyBytesWriter_FinishWithSize(writer, strlen(buf));
 }
 
+#ifdef HAVE_NCURSESW
+PyDoc_STRVAR(_curses_window_get_wstr__doc__,
+"get_wstr([[y, x,] n=2047])\n"
+"Read a string from the user, with primitive line editing capacity.\n"
+"\n"
+"  y\n"
+"    Y-coordinate.\n"
+"  x\n"
+"    X-coordinate.\n"
+"  n\n"
+"    Maximal number of characters.\n"
+"\n"
+"This is the wide-character variant of getstr(); it returns a str.");
+
+static PyObject *
+PyCursesWindow_get_wstr(PyObject *op, PyObject *args)
+{
+    PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);
+    int rtn, use_xy = 0, y = 0, x = 0;
+    unsigned int max_buf_size = 2048;
+    unsigned int n = max_buf_size - 1;
+
+    if (!curses_clinic_parse_optional_xy_n(args, &y, &x, &n, &use_xy,
+                                           "_curses.window.get_wstr"))
+    {
+        return NULL;
+    }
+
+    n = Py_MIN(n, max_buf_size - 1);
+    wint_t *buf = PyMem_New(wint_t, n + 1);
+    if (buf == NULL) {
+        return PyErr_NoMemory();
+    }
+
+    if (use_xy) {
+        Py_BEGIN_ALLOW_THREADS
+        rtn = mvwgetn_wstr(self->win, y, x, buf, n);
+        Py_END_ALLOW_THREADS
+    }
+    else {
+        Py_BEGIN_ALLOW_THREADS
+        rtn = wgetn_wstr(self->win, buf, n);
+        Py_END_ALLOW_THREADS
+    }
+
+    if (rtn == ERR) {
+        PyMem_Free(buf);
+        return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
+    }
+
+    /* wgetn_wstr() fills a wint_t buffer; copy it to a wchar_t buffer. */
+    Py_ssize_t len = 0;
+    while (buf[len]) {
+        len++;
+    }
+    wchar_t *wbuf = PyMem_New(wchar_t, len + 1);
+    if (wbuf == NULL) {
+        PyMem_Free(buf);
+        return PyErr_NoMemory();
+    }
+    for (Py_ssize_t i = 0; i < len; i++) {
+        wbuf[i] = (wchar_t)buf[i];
+    }
+    PyObject *res = PyUnicode_FromWideChar(wbuf, len);
+    PyMem_Free(wbuf);
+    PyMem_Free(buf);
+    return res;
+}
+
+PyDoc_STRVAR(_curses_window_in_wstr__doc__,
+"in_wstr([y, x,] n=2047)\n"
+"Return a string of characters, extracted from the window.\n"
+"\n"
+"  y\n"
+"    Y-coordinate.\n"
+"  x\n"
+"    X-coordinate.\n"
+"  n\n"
+"    Maximal number of characters.\n"
+"\n"
+"This is the wide-character variant of instr(); it returns a str.");
+
+static PyObject *
+PyCursesWindow_in_wstr(PyObject *op, PyObject *args)
+{
+    PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);
+    int rtn, use_xy = 0, y = 0, x = 0;
+    unsigned int max_buf_size = 2048;
+    unsigned int n = max_buf_size - 1;
+
+    if (!curses_clinic_parse_optional_xy_n(args, &y, &x, &n, &use_xy,
+                                           "_curses.window.in_wstr"))
+    {
+        return NULL;
+    }
+
+    n = Py_MIN(n, max_buf_size - 1);
+    wchar_t *buf = PyMem_New(wchar_t, n + 1);
+    if (buf == NULL) {
+        return PyErr_NoMemory();
+    }
+
+    if (use_xy) {
+        rtn = mvwinnwstr(self->win, y, x, buf, n);
+    }
+    else {
+        rtn = winnwstr(self->win, buf, n);
+    }
+
+    if (rtn == ERR) {
+        PyMem_Free(buf);
+        return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
+    }
+    PyObject *res = PyUnicode_FromWideChar(buf, -1);
+    PyMem_Free(buf);
+    return res;
+}
+#endif /* HAVE_NCURSESW */
+
 /*[clinic input]
 _curses.window.insstr
 
@@ -2866,15 +3168,28 @@ _curses_window_vline_impl(PyCursesWindowObject *self, int group_left_1,
 /*[clinic end generated code: output=287ad1cc8982217f input=a6f2dc86a4648b32]*/
 {
     chtype ch_;
-
+#ifdef HAVE_NCURSESW
+    cchar_t wch;
+    int type = PyCurses_ConvertToCell(self, ch, attr, "vline", &ch_, &wch);
+    if (type == 0) {
+        return NULL;
+    }
+#else
     if (!PyCurses_ConvertToChtype(self, ch, &ch_))
         return NULL;
+#endif
     if (group_left_1) {
         if (wmove(self->win, y, x) == ERR) {
             curses_window_set_error(self, "wmove", "vline");
             return NULL;
         }
     }
+#ifdef HAVE_NCURSESW
+    if (type == 2) {
+        int rtn = wvline_set(self->win, &wch, n);
+        return curses_window_check_err(self, rtn, "wvline_set", "vline");
+    }
+#endif
     int rtn = wvline(self->win, ch_ | (attr_t)attr, n);
     return curses_window_check_err(self, rtn, "wvline", "vline");
 }
@@ -2983,6 +3298,12 @@ static PyMethodDef PyCursesWindow_methods[] = {
         "getstr", PyCursesWindow_getstr, METH_VARARGS,
         _curses_window_getstr__doc__
     },
+#ifdef HAVE_NCURSESW
+    {
+        "get_wstr", PyCursesWindow_get_wstr, METH_VARARGS,
+        _curses_window_get_wstr__doc__
+    },
+#endif
     {"getyx", PyCursesWindow_getyx, METH_NOARGS,
      "getyx($self, /)\n--\n\n"
      "Return a tuple (y, x) of the current cursor position."},
@@ -3012,6 +3333,12 @@ static PyMethodDef PyCursesWindow_methods[] = {
         "instr", PyCursesWindow_instr, METH_VARARGS,
         _curses_window_instr__doc__
     },
+#ifdef HAVE_NCURSESW
+    {
+        "in_wstr", PyCursesWindow_in_wstr, METH_VARARGS,
+        _curses_window_in_wstr__doc__
+    },
+#endif
     _CURSES_WINDOW_IS_LINETOUCHED_METHODDEF
     {"is_wintouched", PyCursesWindow_is_wintouched, METH_NOARGS,
      "is_wintouched($self, /)\n--\n\n"
@@ -3495,6 +3822,29 @@ _curses_erasechar_impl(PyObject *module)
     return PyBytes_FromStringAndSize(&ch, 1);
 }
 
+#ifdef HAVE_NCURSESW
+/*[clinic input]
+_curses.erasewchar
+
+Return the user's current wide-character erase character.
+[clinic start generated code]*/
+
+static PyObject *
+_curses_erasewchar_impl(PyObject *module)
+/*[clinic end generated code: output=7f3bd8c9097ac456 input=f7e9a3893b4df2f8]*/
+{
+    wchar_t ch;
+
+    PyCursesStatefulInitialised(module);
+
+    if (erasewchar(&ch) == ERR) {
+        curses_set_error(module, "erasewchar", NULL);
+        return NULL;
+    }
+    return PyUnicode_FromWideChar(&ch, 1);
+}
+#endif /* HAVE_NCURSESW */
+
 /*[clinic input]
 _curses.flash
 
@@ -4227,6 +4577,27 @@ _curses_killchar_impl(PyObject *module)
     return PyBytes_FromStringAndSize(&ch, 1);
 }
 
+#ifdef HAVE_NCURSESW
+/*[clinic input]
+_curses.killwchar
+
+Return the user's current wide-character line kill character.
+[clinic start generated code]*/
+
+static PyObject *
+_curses_killwchar_impl(PyObject *module)
+/*[clinic end generated code: output=eac1fd72a0c88d42 input=5c2d7d1ab2f24eb7]*/
+{
+    wchar_t ch;
+
+    if (killwchar(&ch) == ERR) {
+        curses_set_error(module, "killwchar", NULL);
+        return NULL;
+    }
+    return PyUnicode_FromWideChar(&ch, 1);
+}
+#endif /* HAVE_NCURSESW */
+
 /*[clinic input]
 _curses.longname
 
@@ -5081,6 +5452,52 @@ _curses_unctrl(PyObject *module, PyObject *ch)
     return PyBytes_FromString(res);
 }
 
+#ifdef HAVE_NCURSESW
+/*[clinic input]
+_curses.wunctrl
+
+    ch: object
+    /
+
+Return a printable representation of the wide character ch.
+
+Control characters are displayed as a caret followed by the character,
+for example as ^C.  Printing characters are left as they are.
+[clinic start generated code]*/
+
+static PyObject *
+_curses_wunctrl(PyObject *module, PyObject *ch)
+/*[clinic end generated code: output=7b16d5534ff05728 input=9ceb6749118bd07c]*/
+{
+    chtype ch_;
+    wchar_t wstr[CCHARW_MAX + 1];
+    cchar_t wcval;
+
+    PyCursesStatefulInitialised(module);
+
+    int type = PyCurses_ConvertToCchar_t(NULL, ch, &ch_, wstr);
+    if (type == 0) {
+        return NULL;
+    }
+    if (type == 1) {
+        /* A narrow character is the spacing character of the cell. */
+        wstr[0] = (wchar_t)(ch_ & A_CHARTEXT);
+        wstr[1] = L'\0';
+    }
+    if (setcchar(&wcval, wstr, A_NORMAL, 0, NULL) == ERR) {
+        curses_set_error(module, "setcchar", "wunctrl");
+        return NULL;
+    }
+
+    wchar_t *res = wunctrl(&wcval);
+    if (res == NULL) {
+        curses_set_null_error(module, "wunctrl", NULL);
+        return NULL;
+    }
+    return PyUnicode_FromWideChar(res, -1);
+}
+#endif /* HAVE_NCURSESW */
+
 /*[clinic input]
 _curses.ungetch
 
@@ -5342,6 +5759,7 @@ static PyMethodDef cursesmodule_methods[] = {
     _CURSES_ECHO_METHODDEF
     _CURSES_ENDWIN_METHODDEF
     _CURSES_ERASECHAR_METHODDEF
+    _CURSES_ERASEWCHAR_METHODDEF
     _CURSES_FILTER_METHODDEF
     _CURSES_NOFILTER_METHODDEF
     _CURSES_FLASH_METHODDEF
@@ -5364,6 +5782,7 @@ static PyMethodDef cursesmodule_methods[] = {
     _CURSES_IS_TERM_RESIZED_METHODDEF
     _CURSES_KEYNAME_METHODDEF
     _CURSES_KILLCHAR_METHODDEF
+    _CURSES_KILLWCHAR_METHODDEF
     _CURSES_LONGNAME_METHODDEF
     _CURSES_META_METHODDEF
     _CURSES_MOUSEINTERVAL_METHODDEF
@@ -5405,6 +5824,7 @@ static PyMethodDef cursesmodule_methods[] = {
     _CURSES_TPARM_METHODDEF
     _CURSES_TYPEAHEAD_METHODDEF
     _CURSES_UNCTRL_METHODDEF
+    _CURSES_WUNCTRL_METHODDEF
     _CURSES_UNGETCH_METHODDEF
     _CURSES_UPDATE_LINES_COLS_METHODDEF
     _CURSES_UNGET_WCH_METHODDEF
index f577368680ef572ed26afc13c32a5c4619d60d22..62dab279f5a97c0bea9e4b5008e196afd597e53c 100644 (file)
@@ -2267,6 +2267,28 @@ _curses_erasechar(PyObject *module, PyObject *Py_UNUSED(ignored))
     return _curses_erasechar_impl(module);
 }
 
+#if defined(HAVE_NCURSESW)
+
+PyDoc_STRVAR(_curses_erasewchar__doc__,
+"erasewchar($module, /)\n"
+"--\n"
+"\n"
+"Return the user\'s current wide-character erase character.");
+
+#define _CURSES_ERASEWCHAR_METHODDEF    \
+    {"erasewchar", (PyCFunction)_curses_erasewchar, METH_NOARGS, _curses_erasewchar__doc__},
+
+static PyObject *
+_curses_erasewchar_impl(PyObject *module);
+
+static PyObject *
+_curses_erasewchar(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+    return _curses_erasewchar_impl(module);
+}
+
+#endif /* defined(HAVE_NCURSESW) */
+
 PyDoc_STRVAR(_curses_flash__doc__,
 "flash($module, /)\n"
 "--\n"
@@ -3090,6 +3112,28 @@ _curses_killchar(PyObject *module, PyObject *Py_UNUSED(ignored))
     return _curses_killchar_impl(module);
 }
 
+#if defined(HAVE_NCURSESW)
+
+PyDoc_STRVAR(_curses_killwchar__doc__,
+"killwchar($module, /)\n"
+"--\n"
+"\n"
+"Return the user\'s current wide-character line kill character.");
+
+#define _CURSES_KILLWCHAR_METHODDEF    \
+    {"killwchar", (PyCFunction)_curses_killwchar, METH_NOARGS, _curses_killwchar__doc__},
+
+static PyObject *
+_curses_killwchar_impl(PyObject *module);
+
+static PyObject *
+_curses_killwchar(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+    return _curses_killwchar_impl(module);
+}
+
+#endif /* defined(HAVE_NCURSESW) */
+
 PyDoc_STRVAR(_curses_longname__doc__,
 "longname($module, /)\n"
 "--\n"
@@ -4266,6 +4310,22 @@ PyDoc_STRVAR(_curses_unctrl__doc__,
 #define _CURSES_UNCTRL_METHODDEF    \
     {"unctrl", (PyCFunction)_curses_unctrl, METH_O, _curses_unctrl__doc__},
 
+#if defined(HAVE_NCURSESW)
+
+PyDoc_STRVAR(_curses_wunctrl__doc__,
+"wunctrl($module, ch, /)\n"
+"--\n"
+"\n"
+"Return a printable representation of the wide character ch.\n"
+"\n"
+"Control characters are displayed as a caret followed by the character,\n"
+"for example as ^C.  Printing characters are left as they are.");
+
+#define _CURSES_WUNCTRL_METHODDEF    \
+    {"wunctrl", (PyCFunction)_curses_wunctrl, METH_O, _curses_wunctrl__doc__},
+
+#endif /* defined(HAVE_NCURSESW) */
+
 PyDoc_STRVAR(_curses_ungetch__doc__,
 "ungetch($module, ch, /)\n"
 "--\n"
@@ -4437,6 +4497,10 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored
     #define _CURSES_NOFILTER_METHODDEF
 #endif /* !defined(_CURSES_NOFILTER_METHODDEF) */
 
+#ifndef _CURSES_ERASEWCHAR_METHODDEF
+    #define _CURSES_ERASEWCHAR_METHODDEF
+#endif /* !defined(_CURSES_ERASEWCHAR_METHODDEF) */
+
 #ifndef _CURSES_GETSYX_METHODDEF
     #define _CURSES_GETSYX_METHODDEF
 #endif /* !defined(_CURSES_GETSYX_METHODDEF) */
@@ -4473,6 +4537,10 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored
     #define _CURSES_IS_TERM_RESIZED_METHODDEF
 #endif /* !defined(_CURSES_IS_TERM_RESIZED_METHODDEF) */
 
+#ifndef _CURSES_KILLWCHAR_METHODDEF
+    #define _CURSES_KILLWCHAR_METHODDEF
+#endif /* !defined(_CURSES_KILLWCHAR_METHODDEF) */
+
 #ifndef _CURSES_MOUSEINTERVAL_METHODDEF
     #define _CURSES_MOUSEINTERVAL_METHODDEF
 #endif /* !defined(_CURSES_MOUSEINTERVAL_METHODDEF) */
@@ -4501,6 +4569,10 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored
     #define _CURSES_TYPEAHEAD_METHODDEF
 #endif /* !defined(_CURSES_TYPEAHEAD_METHODDEF) */
 
+#ifndef _CURSES_WUNCTRL_METHODDEF
+    #define _CURSES_WUNCTRL_METHODDEF
+#endif /* !defined(_CURSES_WUNCTRL_METHODDEF) */
+
 #ifndef _CURSES_UNGET_WCH_METHODDEF
     #define _CURSES_UNGET_WCH_METHODDEF
 #endif /* !defined(_CURSES_UNGET_WCH_METHODDEF) */
@@ -4516,4 +4588,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=0bce70b538541c9e input=a9049054013a1b77]*/