Skip to content

Commit ffcc80f

Browse files
authored
Merge pull request #130 from ruby/feature/extended-colors
Support ncurses extended colors API
2 parents 5e27a02 + c408e11 commit ffcc80f

File tree

4 files changed

+118
-22
lines changed

4 files changed

+118
-22
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Type the following command, and see `[rdoc]` of curses:
5353

5454
## Limitations
5555

56-
* curses.gem doesn't support more than 256 color pairs. See https://reversed.top/2019-02-05/more-than-256-curses-color-pairs/ for details.
56+
* On ncurses 6+ compiled with extended color support, more than 256 color pairs are supported transparently via the extended colors API (`init_extended_pair`, etc.). Use `Curses.support_extended_colors?` to check at runtime.
5757

5858
## Developers
5959

ext/curses/curses.c

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,7 +1321,11 @@ curses_init_pair(VALUE obj, VALUE pair, VALUE f, VALUE b)
13211321
{
13221322
/* may have to raise exception on ERR */
13231323
curses_stdscr();
1324+
#ifdef HAVE_INIT_EXTENDED_PAIR
1325+
return (init_extended_pair(NUM2INT(pair), NUM2INT(f), NUM2INT(b)) == OK) ? Qtrue : Qfalse;
1326+
#else
13241327
return (init_pair(NUM2INT(pair),NUM2INT(f),NUM2INT(b)) == OK) ? Qtrue : Qfalse;
1328+
#endif
13251329
}
13261330

13271331
/*
@@ -1345,8 +1349,13 @@ curses_init_color(VALUE obj, VALUE color, VALUE r, VALUE g, VALUE b)
13451349
{
13461350
/* may have to raise exception on ERR */
13471351
curses_stdscr();
1352+
#ifdef HAVE_INIT_EXTENDED_COLOR
1353+
return (init_extended_color(NUM2INT(color), NUM2INT(r),
1354+
NUM2INT(g), NUM2INT(b)) == OK) ? Qtrue : Qfalse;
1355+
#else
13481356
return (init_color(NUM2INT(color),NUM2INT(r),
13491357
NUM2INT(g),NUM2INT(b)) == OK) ? Qtrue : Qfalse;
1358+
#endif
13501359
}
13511360

13521361
/*
@@ -1397,11 +1406,22 @@ curses_colors(VALUE obj)
13971406
static VALUE
13981407
curses_color_content(VALUE obj, VALUE color)
13991408
{
1400-
short r,g,b;
1401-
14021409
curses_stdscr();
1403-
color_content(NUM2INT(color),&r,&g,&b);
1404-
return rb_ary_new3(3,INT2FIX(r),INT2FIX(g),INT2FIX(b));
1410+
#ifdef HAVE_EXTENDED_COLOR_CONTENT
1411+
{
1412+
int r, g, b;
1413+
if (extended_color_content(NUM2INT(color), &r, &g, &b) == ERR)
1414+
return Qnil;
1415+
return rb_ary_new3(3, INT2FIX(r), INT2FIX(g), INT2FIX(b));
1416+
}
1417+
#else
1418+
{
1419+
short r, g, b;
1420+
if (color_content(NUM2INT(color), &r, &g, &b) == ERR)
1421+
return Qnil;
1422+
return rb_ary_new3(3, INT2FIX(r), INT2FIX(g), INT2FIX(b));
1423+
}
1424+
#endif
14051425
}
14061426

14071427

@@ -1430,11 +1450,22 @@ curses_color_pairs(VALUE obj)
14301450
static VALUE
14311451
curses_pair_content(VALUE obj, VALUE pair)
14321452
{
1433-
short f,b;
1434-
14351453
curses_stdscr();
1436-
pair_content(NUM2INT(pair),&f,&b);
1437-
return rb_ary_new3(2,INT2FIX(f),INT2FIX(b));
1454+
#ifdef HAVE_EXTENDED_PAIR_CONTENT
1455+
{
1456+
int f, b;
1457+
if (extended_pair_content(NUM2INT(pair), &f, &b) == ERR)
1458+
return Qnil;
1459+
return rb_ary_new3(2, INT2FIX(f), INT2FIX(b));
1460+
}
1461+
#else
1462+
{
1463+
short f, b;
1464+
if (pair_content(NUM2INT(pair), &f, &b) == ERR)
1465+
return Qnil;
1466+
return rb_ary_new3(2, INT2FIX(f), INT2FIX(b));
1467+
}
1468+
#endif
14381469
}
14391470

14401471
/*
@@ -1465,6 +1496,41 @@ curses_pair_number(VALUE obj, VALUE attrs)
14651496
curses_stdscr();
14661497
return INT2FIX(PAIR_NUMBER(NUM2CHTYPE(attrs)));
14671498
}
1499+
1500+
/*
1501+
* Document-method: Curses.support_extended_colors?
1502+
*
1503+
* Returns +true+ if the ncurses library was compiled with extended color
1504+
* support (i.e., init_extended_pair, init_extended_color, etc. are available),
1505+
* +false+ otherwise.
1506+
*/
1507+
static VALUE
1508+
curses_support_extended_colors(VALUE obj)
1509+
{
1510+
#if defined(HAVE_INIT_EXTENDED_PAIR) && defined(HAVE_INIT_EXTENDED_COLOR) && \
1511+
defined(HAVE_EXTENDED_COLOR_CONTENT) && defined(HAVE_EXTENDED_PAIR_CONTENT)
1512+
return Qtrue;
1513+
#else
1514+
return Qfalse;
1515+
#endif
1516+
}
1517+
1518+
/*
1519+
* Document-method: Curses.reset_color_pairs
1520+
*
1521+
* Resets all color pairs to undefined. Requires ncurses 6.1+.
1522+
*/
1523+
#ifdef HAVE_RESET_COLOR_PAIRS
1524+
static VALUE
1525+
curses_reset_color_pairs(VALUE obj)
1526+
{
1527+
curses_stdscr();
1528+
reset_color_pairs();
1529+
return Qnil;
1530+
}
1531+
#else
1532+
#define curses_reset_color_pairs rb_f_notimplement
1533+
#endif
14681534
#endif /* USE_COLOR */
14691535

14701536
#ifdef USE_MOUSE
@@ -2717,7 +2783,7 @@ window_setscrreg(VALUE obj, VALUE top, VALUE bottom)
27172783
#endif
27182784
}
27192785

2720-
#if defined(USE_COLOR) && defined(HAVE_WCOLOR_SET)
2786+
#if defined(USE_COLOR) && (defined(HAVE_WCOLOR_SET) || defined(HAVE_WATTR_SET))
27212787
/*
27222788
* Document-method: Curses::Window.color_set
27232789
* call-seq: color_set(col)
@@ -2729,13 +2795,28 @@ static VALUE
27292795
window_color_set(VALUE obj, VALUE col)
27302796
{
27312797
struct windata *winp;
2732-
int res;
27332798

27342799
GetWINDOW(obj, winp);
2735-
res = wcolor_set(winp->window, NUM2INT(col), NULL);
2736-
return (res == OK) ? Qtrue : Qfalse;
2800+
#if defined(HAVE_WATTR_SET) && defined(HAVE_WATTR_GET)
2801+
/* Use wattr_set to support pair numbers > 255; preserve existing attrs. */
2802+
{
2803+
attr_t attrs;
2804+
#ifdef NCURSES_PAIRS_T
2805+
NCURSES_PAIRS_T current_pair;
2806+
#else
2807+
short current_pair;
2808+
#endif
2809+
if (wattr_get(winp->window, &attrs, &current_pair, NULL) == ERR)
2810+
return Qfalse;
2811+
return (wattr_set(winp->window, attrs, NUM2INT(col), NULL) == OK) ? Qtrue : Qfalse;
2812+
}
2813+
#elif defined(HAVE_WATTR_SET)
2814+
return (wattr_set(winp->window, 0, NUM2INT(col), NULL) == OK) ? Qtrue : Qfalse;
2815+
#else
2816+
return (wcolor_set(winp->window, NUM2INT(col), NULL) == OK) ? Qtrue : Qfalse;
2817+
#endif
27372818
}
2738-
#endif /* defined(USE_COLOR) && defined(HAVE_WCOLOR_SET) */
2819+
#endif /* defined(USE_COLOR) && (defined(HAVE_WCOLOR_SET) || defined(HAVE_WATTR_SET)) */
27392820

27402821
/*
27412822
* Document-method: Curses::Window.scroll
@@ -5071,6 +5152,8 @@ Init_curses(void)
50715152
rb_define_module_function(mCurses, "pair_content", curses_pair_content, 1);
50725153
rb_define_module_function(mCurses, "color_pair", curses_color_pair, 1);
50735154
rb_define_module_function(mCurses, "pair_number", curses_pair_number, 1);
5155+
rb_define_module_function(mCurses, "support_extended_colors?", curses_support_extended_colors, 0);
5156+
rb_define_module_function(mCurses, "reset_color_pairs", curses_reset_color_pairs, 0);
50745157
#endif /* USE_COLOR */
50755158
#ifdef USE_MOUSE
50765159
rb_define_module_function(mCurses, "getmouse", curses_getmouse, 0);
@@ -5203,9 +5286,9 @@ Init_curses(void)
52035286
rb_define_method(cWindow, "move", window_move, 2);
52045287
rb_define_method(cWindow, "move_relative", window_move_relative, 2);
52055288
rb_define_method(cWindow, "setpos", window_setpos, 2);
5206-
#if defined(USE_COLOR) && defined(HAVE_WCOLOR_SET)
5289+
#if defined(USE_COLOR) && (defined(HAVE_WCOLOR_SET) || defined(HAVE_WATTR_SET))
52075290
rb_define_method(cWindow, "color_set", window_color_set, 1);
5208-
#endif /* USE_COLOR && HAVE_WCOLOR_SET */
5291+
#endif /* USE_COLOR && (HAVE_WCOLOR_SET || HAVE_WATTR_SET) */
52095292
rb_define_method(cWindow, "cury", window_cury, 0);
52105293
rb_define_method(cWindow, "curx", window_curx, 0);
52115294
rb_define_method(cWindow, "maxy", window_maxy, 0);

ext/curses/extconf.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ def exec_command(cmd)
119119
def_prog_mode reset_prog_mode timeout wtimeout nodelay
120120
init_color wcolor_set use_default_colors assume_default_colors
121121
newpad unget_wch get_wch wget_wch PDC_get_key_modifiers
122-
chgat wchgat newterm)
122+
chgat wchgat newterm
123+
init_extended_color init_extended_pair extended_color_content
124+
extended_pair_content reset_color_pairs wattr_set wattr_get)
123125
have_func(f) || (have_macro(f, curses) && $defs.push(format("-DHAVE_%s", f.upcase)))
124126
end
125127
convertible_int('chtype', [["#undef MOUSE_MOVED\n"]]+curses) or abort

sample/colors.rb

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
# The TERM environment variable should be set to xterm-256color etc. to
77
# use 256 colors. Curses.colors returns the color numbers of the terminal.
8+
# With ncurses 6+ extended color support, color_pairs may exceed 256.
89

910
begin
1011
init_screen
@@ -14,14 +15,24 @@
1415
else
1516
start_color
1617

17-
addstr "This Terminal supports #{colors} colors.\n"
18-
19-
Curses.colors.times { |i|
20-
Curses.init_pair(i, i, 0)
21-
attrset(color_pair(i))
18+
extended = Curses.support_extended_colors?
19+
addstr "This Terminal supports #{colors} colors, #{color_pairs} pairs"
20+
addstr extended ? " (extended).\n" : ".\n"
21+
22+
(extended ? [512, color_pairs].min : colors).times { |i|
23+
next if i == 0
24+
Curses.init_pair(i, i % colors, (i / colors) % colors)
25+
if extended
26+
# color_pair() encodes into chtype and can't handle pairs > 255;
27+
# use color_set on stdscr instead, which calls wattr_set internally.
28+
stdscr.color_set(i)
29+
else
30+
attrset(color_pair(i))
31+
end
2232
addstr("#{i.to_s.rjust(3)} ")
2333
addstr("\n") if i == 15 || (i > 16 && (i - 15) % 36 == 0)
2434
}
35+
stdscr.color_set(0)
2536
end
2637

2738
getch

0 commit comments

Comments
 (0)