Those who used curses at least once to draw something in color had to notice that setting colors happens in pairs (foreground and background) and number of such pairs is limited. These limitations and some basics about upgrading code to lift one of the limits will be described in this short post.
The 256 color pairs limit
The first issue is that while 256 color pairs are enough to hold all color configurations of 16-colors (16 foreground * 16 background = 256), they become scarce resource that requires managing on part of your application when number of available colors is greater (usually 256 these days).
The second issue is that original color API (COLOR_PAIR()
macro) didn’t
account for possibility of having more than 256 color pairs and your application
needs changes to be able to handle this case or it might work incorrectly
(COLOR_PAIR()
just can’t handle large color pair numbers, yet you’ll feed them
to it).
The third and last piece is that most of curses examples you’ll find are using old API and there seems to be no (at least easy to find) documentation on how to transition to the new one. There are applications which use new API, it’s just documentation that didn’t really caught up.
Historical context
ncurses FAQ provides extensive details on the subject in this entry.
My misconceptions
I guess someone else might also have them.
What’s limiting color pairs
I used to think that color pairs limit exists only at the level of the library
implementation, however it’s not that hard to find documentation referencing
max_pairs
entry of terminfo database, which is fetched by
curses on call to start_color()
into COLOR_PAIRS
variable.
Looking up max_pairs
variable in man terminfo
will give you its capability
name making it easy to check how much color pairs you can get with current
$TERM
setting:
$ tput pairs 32767
You can’t just switch to new API
Apparently, you can. Replacing all uses of COLOR_PAIR()
with equivalents
should be fine and everything should continue to work as before. However,
that’s not enough to get more than 256 color pairs working, library itself
should be built for that (see below). It’s also available for many years at
least in ncurses, which has it since 2005.
If new API is there, all color pairs are available
No, even if it’s there, you might be limited to 256 color pairs at most. Supporting more pairs requires changes in types, which means API/ABI breakage. In ncurses new ABI has version 6, yet this doesn’t mean version 6 of the library. ncurses 6 with wide support can be built to support large color pair numbers, see this comment from Thomas Dickey (its maintainer).
Elements of new API
Original API operates on int
, which might include the following components
ORed together by the client:
- color pair number
- attributes (
A_BOLD
,A_REVERSE
, etc.) - character
New API accepts these things separately (internal representation might be
whatever works) and uses constants for flags that start with WA_
prefix
instead of A_
. And from the client’s perspective there is a new type called
cchar_t
(for “complex character”) and two functions that convert to and from
it:
setcchar()
– packscchar_t
getcchar()
– unpackscchar_t
There are various calls that accept color pair and attributes separately or
operate on cchar_t
. Names lack consistency, so to say:
bkgrndset()
is an equivalent of olderbkgdset()
wattr_set()
is an equivalent of olderwattrset()
- etc., see
curses.h
for interesting functions withcchar_t
and then callman
on them orapropos 'complex character'
(is there a better way?)
Functions of new API also take void *options
parameter along with attributes
and color pair number, which should be NULL
unless you’re using ncurses 6.1
or newer which uses it as an extension for using int
as color pair number (for
true RGB colors; see functions with “extended” in their names).
Examples
Window background
Old API:
wbkgdset(win, COLOR_PAIR(pair) | attrs);
New API:
cchar_t cch; setcchar(&cch, L" ", attrs, pair, NULL); wbkgrndset(win, &cch);
Window attributes
Old API:
wattrset(win, COLOR_PAIR(pair) | attrs);
New API:
wattr_set(win, attrs, pair, NULL);