You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Ncurses Extended Color Pairs incorrectly display as copies of lower 256 color pairs #119138
When using ncurses extended color pairs, introduced in Python 3.10 in PR #17536 and issue #81163, the color pairs past 256 don't seem to work. When accessed, pairs 256+ repeat the behavior of pairs 0-255 instead of the colors set by curses.init_pair().
In my use case, curses.has_extended_color_support() == True and curses.COLORS == 256.
I can use curses.init_pair() to initialize color pairs past 255. It lets me set them without any error. But when I go to display the colors, color pair 256 displays the same as color pair 0, 257 displays the same as 1, and so on.
The test program initializes 256 * 256 (65536) color pairs, using every FG and BG combination for FG and BG colors 0-255. It then lets you use the left/right arrow keys to scroll through the pairs. It requires an xterm-256color capable terminal.
In the following screenshot, the test program shows each color pair number in its given color and you can see that the pairs repeat. The expected behavior is that pairs 256-512 should have a different background color set (red), as should the next 256 pairs (green), etc.
Edit: If you get an error running the test program, make your terminal larger.
changed the title [-]Ncurses Extended Color Pairs not working, repeating[/-][+]Ncurses Extended Color Pairs incorrectly display as copies of lower 256 color pairs[/+]on May 18, 2024
The addstr and addch functions take an attribute value, but attributes can only store a color pair in the range 0..255. COLOR_PAIR() applies a bitmask so it can be combined with other attributes. I think we need new API to allow users to pass in the color pair separately and use wattr_set() internally instead of the legacy wattrset(). Ref. attr(3NCURSES).
@hpjansson why do we have to maintain an older api with wattrset() if wattr_set() can do all the same things, and more. And, we never expose that to the user directly can't we just update the binding to use wattr_set() under the hood and fix this?
why do we have to maintain an older api with wattrset() if wattr_set() can do all the same things,
Because not everyone has access to the new API. So we need to maintain whatever is still being used in the wild.
I think we need new API to allow users to pass in the color pair separately and use wattr_set() internally instead of the legacy wattrset().
I can think of this. Or we can call this one when we pass a color attribute internally so that we don't have a new public API. I'll need to think about it.
Because not everyone has access to the new API. So we need to maintain whatever is still being used in the wild.
When you say "new API" here you mean a new enough version of ncurses? If so, can't we just provide different definitions for the same function -- one which uses the new ncurses API and one which uses the old one? That's pretty routine for C. Then if you have the new API, the advanced color functionality just works because wattr_set() is called under the hood.
When you say "new API" here you mean a new enough version of ncurses?
Yes, according to the docs, if you want to have more than 256 color pairs, you need ncurses ABI 6 even if you use wattr_set. But maybe we could just make the detection of extended pairs better (namely, if the ABI is < 6 and wattr_set does not exist => we say we cannot support extended pairs).
maybe we could just make the detection of extended pairs better (namely, if the ABI is < 6 and wattr_set does not exist => we say we cannot support extended pairs).
I reckon that since the documentation already hides both ABI versions behind one Python interface, then this would be a nice way for reality to reflect the documentation, if possible.
To me this seems preferable over making a new interface on the Python side, as people are already programming based on the documentation. It's also less work for people using the interface, if a bit more work behind-the-scenes on the Cpython side.
This approach deviates somewhat from the C ncurses patterns to be more Pythonic, though. I think that is cool if it works, but bad if it doesn't. Right now it doesn't.
Dumb question, but will this have implications for other Python implementations? Are there any non-cpython issues to take into consideration?
Is there any way to ask Python which Ncurses ABI it is using? I kind of assumed that's what curses.has_extended_color_support() would do, by proxy. (At least, it should always be False when using the Version 5 ABI.)
will this have implications for other Python implementations
This is an extension module, entirely optional, and I believe that PyPy and others just ships the ncurses module. The only thing we touch is the curses API calls which are independent of how Python is implemented. Unless other implementations decideed to reimplement the curses module (namely provide their own shared library), then there won't be any impact. If they do, there will be a difference between our and the other implementations but that's their job if they want to backport the new feature.
This approach deviates somewhat from the C ncurses patterns to be more Pythonic, though
Actually, that's what we already do internally. When we can, we use new curses ABI calls internally and when we can't then we don't. For instance, for addstr:
The biggest difference is that the Python interface makes things simpler by merging different C functions such as addstr(), mvaddstr(), and mvwaddstr() into a single addstr() method.
So we're already unifying multiple kind of calls like this. In addition, we don't expose specific wattrset and wattr_set, so we're already saying "whatever attribute you want, we'll handle it ourselves".
More generally, I don't think we wanted the user to make the distinction between whether the attribute is passed as a 8-bit integer or not. That's what we also expose a way to create color attributes. I therefore think it's kind of a bug on our side that.
@hpjansson why do we have to maintain an older api with wattrset() if wattr_set() can do all the same things, and more. And, we never expose that to the user directly can't we just update the binding to use wattr_set() under the hood and fix this?
I agree this would be the best solution, as long as it can be done without breaking existing code. I was also worried that it'd break the thin wrapper pattern of the ncurses module, but as @picnixz pointed out, we're already merging calls, so that's probably not an issue.
The pair was passed packed into a chtype via curses.color_pair(), whose color field only holds about 8 bits, so pairs ≥ 256 wrap to the lower 256. That is why they set correctly but display wrong.
Python 3.16 adds APIs that pass the color pair as a separate argument instead of packing it, so pairs ≥ 256 work:
Activity
[-]Ncurses Extended Color Pairs not working, repeating[/-][+]Ncurses Extended Color Pairs incorrectly display as copies of lower 256 color pairs[/+]hpjansson commentedon May 24, 2024
The
addstrandaddchfunctions take an attribute value, but attributes can only store a color pair in the range 0..255.COLOR_PAIR()applies a bitmask so it can be combined with other attributes. I think we need new API to allow users to pass in the color pair separately and usewattr_set()internally instead of the legacywattrset(). Ref.attr(3NCURSES).EvanCarroll commentedon Dec 5, 2024
@hpjansson why do we have to maintain an older api with
wattrset()ifwattr_set()can do all the same things, and more. And, we never expose that to the user directly can't we just update the binding to usewattr_set()under the hood and fix this?grymmjack commentedon Dec 6, 2024
Would love to know the status of this!
picnixz commentedon Dec 6, 2024
Because not everyone has access to the new API. So we need to maintain whatever is still being used in the wild.
I can think of this. Or we can call this one when we pass a color attribute internally so that we don't have a new public API. I'll need to think about it.
EvanCarroll commentedon Dec 6, 2024
When you say "new API" here you mean a new enough version of ncurses? If so, can't we just provide different definitions for the same function -- one which uses the new ncurses API and one which uses the old one? That's pretty routine for C. Then if you have the new API, the advanced color functionality just works because
wattr_set()is called under the hood.picnixz commentedon Dec 6, 2024
Yes, according to the docs, if you want to have more than 256 color pairs, you need ncurses ABI 6 even if you use
wattr_set. But maybe we could just make the detection of extended pairs better (namely, if the ABI is < 6 andwattr_setdoes not exist => we say we cannot support extended pairs).cmang commentedon Dec 7, 2024
I reckon that since the documentation already hides both ABI versions behind one Python interface, then this would be a nice way for reality to reflect the documentation, if possible.
To me this seems preferable over making a new interface on the Python side, as people are already programming based on the documentation. It's also less work for people using the interface, if a bit more work behind-the-scenes on the Cpython side.
This approach deviates somewhat from the C ncurses patterns to be more Pythonic, though. I think that is cool if it works, but bad if it doesn't. Right now it doesn't.
Dumb question, but will this have implications for other Python implementations? Are there any non-cpython issues to take into consideration?
Is there any way to ask Python which Ncurses ABI it is using? I kind of assumed that's what curses.has_extended_color_support() would do, by proxy. (At least, it should always be False when using the Version 5 ABI.)
picnixz commentedon Dec 7, 2024
This is an extension module, entirely optional, and I believe that PyPy and others just ships the ncurses module. The only thing we touch is the curses API calls which are independent of how Python is implemented. Unless other implementations decideed to reimplement the curses module (namely provide their own shared library), then there won't be any impact. If they do, there will be a difference between our and the other implementations but that's their job if they want to backport the new feature.
Actually, that's what we already do internally. When we can, we use new curses ABI calls internally and when we can't then we don't. For instance, for
addstr:So we're already unifying multiple kind of calls like this. In addition, we don't expose specific
wattrsetandwattr_set, so we're already saying "whatever attribute you want, we'll handle it ourselves".More generally, I don't think we wanted the user to make the distinction between whether the attribute is passed as a 8-bit integer or not. That's what we also expose a way to create color attributes. I therefore think it's kind of a bug on our side that.
hpjansson commentedon Dec 7, 2024
I agree this would be the best solution, as long as it can be done without breaking existing code. I was also worried that it'd break the thin wrapper pattern of the ncurses module, but as @picnixz pointed out, we're already merging calls, so that's probably not an issue.
janEntikan commentedon Jun 22, 2025
I'd also like to be able to use 256 background colors.
serhiy-storchaka commentedon Jun 26, 2026
The pair was passed packed into a
chtypeviacurses.color_pair(), whose color field only holds about 8 bits, so pairs ≥ 256 wrap to the lower 256. That is why they set correctly but display wrong.Python 3.16 adds APIs that pass the color pair as a separate argument instead of packing it, so pairs ≥ 256 work:
window.color_set(pair)/window.attr_set(attr, pair)(Add curses window attribute get/set methods and WA_* constants #152219)complexchar(text, attr, pair)/complexstr, accepted byaddch()/addstr()(Add the cchar_t (complex character) API to the curses module #152233)So your example can be adapted by setting the pair separately:
or packing it with text:
serhiy-storchaka commentedon Jun 26, 2026
Closing as addressed in 3.16.
cmang commentedon Oct 19, 2026
For the sake of completeness, here is the test program updated to work with Python 3.16 using the new stdscr.color_set() method:
https://gist.github.com/cmang/3b18474a1867d076596b30dd1dc86d96
Tested with Python 3.16.0a0 in Ubuntu 26.04.1 LTS.