The curses character-cell window methods currently accept only a single narrow character -- an int, a one-byte bytes, or a one-character str -- and most of the wide-character (cchar_t) part of the X/Open Curses API is unwrapped. A character cell can actually hold a full complex character: one spacing character optionally followed by combining characters. There is also no way to read text back as str rather than bytes, nor to query the wide erase/kill characters or get a printable representation of a wide character.
I propose to wrap the wide-character API, available when CPython is built against the wide-character ncursesw library.
The character-cell methods addch(), bkgd(), bkgdset(), border(), box(), echochar(), hline(), insch() and vline() additionally accept a str of a spacing character optionally followed by combining characters, dispatching to the ncursesw wide-character functions (wadd_wch(), wbkgrnd(), wborder_set(), wecho_wchar(), whline_set(), wins_wch(), wvline_set(), ...). border() and box() cannot mix integer or byte characters with wide-string characters in a single call. A cell is exactly one spacing character plus zero or more combining characters, so any extra spacing or control character (for example "ab", an emoji ZWJ sequence, or an emoji with a skin-tone modifier) is rejected with ValueError instead of being silently truncated by setcchar().
The new read methods window.get_wstr() and window.in_wstr() are the wide-character counterparts of window.getstr() and window.instr(), returning a str rather than a bytes object.
The new module functions curses.erasewchar(), curses.killwchar() and curses.wunctrl() are the wide-character counterparts of curses.erasechar(), curses.killchar() and curses.unctrl(), returning a str rather than a bytes object.
Hmm, not exactly. It adds a sibling in_wstr() which returns a string. This solves the issue of wide characters, but does not cover all use all power of cchar_t -- characters+attributes+colors. I am planning to do this in separate issue. Most likely I'll introduce a special type for "string with attributes and colors". Adding in_wch() is postponed until finishing the design.
======================================================================
ERROR: test_addch_combining (test.test_curses.TestCurses.test_addch_combining)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 39, in wrapped
test(self, *args, **kwargs)
~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 261, in test_addch_combining
stdscr.addch('e\u0301') # 'e' + COMBINING ACUTE ACCENT
~~~~~~~~~~~~^^^^^^^^^^^
ValueError: a character cell must be a single spacing character optionally followed by combining characters
======================================================================
ERROR: test_addch_emoji (test.test_curses.TestCurses.test_addch_emoji)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 39, in wrapped
test(self, *args, **kwargs)
~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 282, in test_addch_emoji
stdscr.addch(1, 0, '\u263a\ufe0f') # WHITE SMILING FACE + VS-16
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
ValueError: a character cell must be a single spacing character optionally followed by combining characters
======================================================================
ERROR: test_wide_characters (test.test_curses.TestCurses.test_wide_characters)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 39, in wrapped
test(self, *args, **kwargs)
~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 297, in test_wide_characters
stdscr.echochar(combining)
~~~~~~~~~~~~~~~^^^^^^^^^^^
ValueError: a character cell must be a single spacing character optionally followed by combining characters
----------------------------------------------------------------------
test test_curses failed -- Traceback (most recent call last):
File "/Users/raodao/cpython/Lib/test/test_curses.py", line 67, in wrapped
test(self, *args, **kwargs)
~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/raodao/cpython/Lib/test/test_curses.py", line 405, in test_addch_emoji
stdscr.addch(1, 0, '\u263a\ufe0f') # WHITE SMILING FACE + VS-16
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
ValueError: a character cell must be a single spacing character optionally followed by up to 4 combining characters
I did some investigation: Normally, wcwidth(VS16) == 0, for example on Ubuntu 24 and macOS 26. However, on macOS 14.2, wcwidth returns 1 for VS1–VS16.
Do you think we should accommodate older versions of macOS here? If so, I’d be happy to open a PR. One possible fix would be to relax the check for bad in ConvertToWideCell().
Thank you for your report. We cannot relax the check in ConvertToWideCell(), because it is the same check that does ncurser. The only difference is that it silently truncates, while we report an error. The same check is used in complexstr_from_string() which splits string on cells. All three should be consistent to avoid producing garbage. They should also be consistent with the terminal emulator, which most likely uses system wcwidth().
So, the only way is to skip this test on older macOS. #153344 does this. Please check that all other tests passed.
Activity
asottile commented on Jun 23, 2026
I gave up rebasing #17825 after 4 years -- any chance that's in scope too?
serhiy-storchaka commented on Jun 23, 2026
Hmm, not exactly. It adds a sibling in_wstr() which returns a string. This solves the issue of wide characters, but does not cover all use all power of cchar_t -- characters+attributes+colors. I am planning to do this in separate issue. Most likely I'll introduce a special type for "string with attributes and colors". Adding in_wch() is postponed until finishing the design.
gh-151757: Support wide and combining characters in the curses module (…
encukou commented on Jun 24, 2026
We now get these errors on ARM64 Raspbian 3.x:
====================================================================== ERROR: test_addch_combining (test.test_curses.TestCurses.test_addch_combining) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 39, in wrapped test(self, *args, **kwargs) ~~~~^^^^^^^^^^^^^^^^^^^^^^^ File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 261, in test_addch_combining stdscr.addch('e\u0301') # 'e' + COMBINING ACUTE ACCENT ~~~~~~~~~~~~^^^^^^^^^^^ ValueError: a character cell must be a single spacing character optionally followed by combining characters ====================================================================== ERROR: test_addch_emoji (test.test_curses.TestCurses.test_addch_emoji) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 39, in wrapped test(self, *args, **kwargs) ~~~~^^^^^^^^^^^^^^^^^^^^^^^ File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 282, in test_addch_emoji stdscr.addch(1, 0, '\u263a\ufe0f') # WHITE SMILING FACE + VS-16 ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ ValueError: a character cell must be a single spacing character optionally followed by combining characters ====================================================================== ERROR: test_wide_characters (test.test_curses.TestCurses.test_wide_characters) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 39, in wrapped test(self, *args, **kwargs) ~~~~^^^^^^^^^^^^^^^^^^^^^^^ File "/home/stan/buildarea/3.x.stan-raspbian.nondebug/build/Lib/test/test_curses.py", line 297, in test_wide_characters stdscr.echochar(combining) ~~~~~~~~~~~~~~~^^^^^^^^^^^ ValueError: a character cell must be a single spacing character optionally followed by combining characters ----------------------------------------------------------------------serhiy-storchaka commented on Jun 24, 2026
Yes, this is locale-specific. I forgot to test this on non-UTF8-locales. There are precedences, I am working on this.
gh-151757: Fix curses wide-character tests in non-UTF-8 locales (GH-1…
weixlu commented on Jul 8, 2026
@serhiy-storchaka Hi, I found that test_curses fails on macOS 14.2
test test_curses failed -- Traceback (most recent call last): File "/Users/raodao/cpython/Lib/test/test_curses.py", line 67, in wrapped test(self, *args, **kwargs) ~~~~^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/raodao/cpython/Lib/test/test_curses.py", line 405, in test_addch_emoji stdscr.addch(1, 0, '\u263a\ufe0f') # WHITE SMILING FACE + VS-16 ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ ValueError: a character cell must be a single spacing character optionally followed by up to 4 combining charactersI did some investigation: Normally, wcwidth(VS16) == 0, for example on Ubuntu 24 and macOS 26. However, on macOS 14.2, wcwidth returns 1 for VS1–VS16.
Do you think we should accommodate older versions of macOS here? If so, I’d be happy to open a PR. One possible fix would be to relax the check for bad in ConvertToWideCell().
weixlu commented on Jul 8, 2026
I'm running this
import ctypes, ctypes.util libc = ctypes.CDLL(ctypes.util.find_library('c')) wcwidth = libc.wcwidth wcwidth.argtypes = [ctypes.c_wchar] wcwidth.restype = ctypes.c_int print('wcwidth(VS16):', wcwidth('\ufe0f'))And it print 1 rather than 0. I'm using Mac Studio 2023, with macOS Sonoma 14.2, Apple M2 Ultra
serhiy-storchaka commented on Jul 8, 2026
Thank you for your report. We cannot relax the check in ConvertToWideCell(), because it is the same check that does ncurser. The only difference is that it silently truncates, while we report an error. The same check is used in complexstr_from_string() which splits string on cells. All three should be consistent to avoid producing garbage. They should also be consistent with the terminal emulator, which most likely uses system wcwidth().
So, the only way is to skip this test on older macOS. #153344 does this. Please check that all other tests passed.
gh-151757: Skip curses variation-selector test on older macOS (GH-153344