diff --git a/README.md b/README.md index 1e480fe7..9a0fdc00 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Type the following command, and see `[rdoc]` of curses: ## Limitations -* 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. +* 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. ## Developers diff --git a/ext/curses/curses.c b/ext/curses/curses.c index cc8e5040..b5b5233e 100644 --- a/ext/curses/curses.c +++ b/ext/curses/curses.c @@ -1321,7 +1321,11 @@ curses_init_pair(VALUE obj, VALUE pair, VALUE f, VALUE b) { /* may have to raise exception on ERR */ curses_stdscr(); +#ifdef HAVE_INIT_EXTENDED_PAIR + return (init_extended_pair(NUM2INT(pair), NUM2INT(f), NUM2INT(b)) == OK) ? Qtrue : Qfalse; +#else return (init_pair(NUM2INT(pair),NUM2INT(f),NUM2INT(b)) == OK) ? Qtrue : Qfalse; +#endif } /* @@ -1345,8 +1349,13 @@ curses_init_color(VALUE obj, VALUE color, VALUE r, VALUE g, VALUE b) { /* may have to raise exception on ERR */ curses_stdscr(); +#ifdef HAVE_INIT_EXTENDED_COLOR + return (init_extended_color(NUM2INT(color), NUM2INT(r), + NUM2INT(g), NUM2INT(b)) == OK) ? Qtrue : Qfalse; +#else return (init_color(NUM2INT(color),NUM2INT(r), NUM2INT(g),NUM2INT(b)) == OK) ? Qtrue : Qfalse; +#endif } /* @@ -1397,11 +1406,22 @@ curses_colors(VALUE obj) static VALUE curses_color_content(VALUE obj, VALUE color) { - short r,g,b; - curses_stdscr(); - color_content(NUM2INT(color),&r,&g,&b); - return rb_ary_new3(3,INT2FIX(r),INT2FIX(g),INT2FIX(b)); +#ifdef HAVE_EXTENDED_COLOR_CONTENT + { + int r, g, b; + if (extended_color_content(NUM2INT(color), &r, &g, &b) == ERR) + return Qnil; + return rb_ary_new3(3, INT2FIX(r), INT2FIX(g), INT2FIX(b)); + } +#else + { + short r, g, b; + if (color_content(NUM2INT(color), &r, &g, &b) == ERR) + return Qnil; + return rb_ary_new3(3, INT2FIX(r), INT2FIX(g), INT2FIX(b)); + } +#endif } @@ -1430,11 +1450,22 @@ curses_color_pairs(VALUE obj) static VALUE curses_pair_content(VALUE obj, VALUE pair) { - short f,b; - curses_stdscr(); - pair_content(NUM2INT(pair),&f,&b); - return rb_ary_new3(2,INT2FIX(f),INT2FIX(b)); +#ifdef HAVE_EXTENDED_PAIR_CONTENT + { + int f, b; + if (extended_pair_content(NUM2INT(pair), &f, &b) == ERR) + return Qnil; + return rb_ary_new3(2, INT2FIX(f), INT2FIX(b)); + } +#else + { + short f, b; + if (pair_content(NUM2INT(pair), &f, &b) == ERR) + return Qnil; + return rb_ary_new3(2, INT2FIX(f), INT2FIX(b)); + } +#endif } /* @@ -1465,6 +1496,41 @@ curses_pair_number(VALUE obj, VALUE attrs) curses_stdscr(); return INT2FIX(PAIR_NUMBER(NUM2CHTYPE(attrs))); } + +/* + * Document-method: Curses.support_extended_colors? + * + * Returns +true+ if the ncurses library was compiled with extended color + * support (i.e., init_extended_pair, init_extended_color, etc. are available), + * +false+ otherwise. + */ +static VALUE +curses_support_extended_colors(VALUE obj) +{ +#if defined(HAVE_INIT_EXTENDED_PAIR) && defined(HAVE_INIT_EXTENDED_COLOR) && \ + defined(HAVE_EXTENDED_COLOR_CONTENT) && defined(HAVE_EXTENDED_PAIR_CONTENT) + return Qtrue; +#else + return Qfalse; +#endif +} + +/* + * Document-method: Curses.reset_color_pairs + * + * Resets all color pairs to undefined. Requires ncurses 6.1+. + */ +#ifdef HAVE_RESET_COLOR_PAIRS +static VALUE +curses_reset_color_pairs(VALUE obj) +{ + curses_stdscr(); + reset_color_pairs(); + return Qnil; +} +#else +#define curses_reset_color_pairs rb_f_notimplement +#endif #endif /* USE_COLOR */ #ifdef USE_MOUSE @@ -2717,7 +2783,7 @@ window_setscrreg(VALUE obj, VALUE top, VALUE bottom) #endif } -#if defined(USE_COLOR) && defined(HAVE_WCOLOR_SET) +#if defined(USE_COLOR) && (defined(HAVE_WCOLOR_SET) || defined(HAVE_WATTR_SET)) /* * Document-method: Curses::Window.color_set * call-seq: color_set(col) @@ -2729,13 +2795,28 @@ static VALUE window_color_set(VALUE obj, VALUE col) { struct windata *winp; - int res; GetWINDOW(obj, winp); - res = wcolor_set(winp->window, NUM2INT(col), NULL); - return (res == OK) ? Qtrue : Qfalse; +#if defined(HAVE_WATTR_SET) && defined(HAVE_WATTR_GET) + /* Use wattr_set to support pair numbers > 255; preserve existing attrs. */ + { + attr_t attrs; +#ifdef NCURSES_PAIRS_T + NCURSES_PAIRS_T current_pair; +#else + short current_pair; +#endif + if (wattr_get(winp->window, &attrs, ¤t_pair, NULL) == ERR) + return Qfalse; + return (wattr_set(winp->window, attrs, NUM2INT(col), NULL) == OK) ? Qtrue : Qfalse; + } +#elif defined(HAVE_WATTR_SET) + return (wattr_set(winp->window, 0, NUM2INT(col), NULL) == OK) ? Qtrue : Qfalse; +#else + return (wcolor_set(winp->window, NUM2INT(col), NULL) == OK) ? Qtrue : Qfalse; +#endif } -#endif /* defined(USE_COLOR) && defined(HAVE_WCOLOR_SET) */ +#endif /* defined(USE_COLOR) && (defined(HAVE_WCOLOR_SET) || defined(HAVE_WATTR_SET)) */ /* * Document-method: Curses::Window.scroll @@ -5071,6 +5152,8 @@ Init_curses(void) rb_define_module_function(mCurses, "pair_content", curses_pair_content, 1); rb_define_module_function(mCurses, "color_pair", curses_color_pair, 1); rb_define_module_function(mCurses, "pair_number", curses_pair_number, 1); + rb_define_module_function(mCurses, "support_extended_colors?", curses_support_extended_colors, 0); + rb_define_module_function(mCurses, "reset_color_pairs", curses_reset_color_pairs, 0); #endif /* USE_COLOR */ #ifdef USE_MOUSE rb_define_module_function(mCurses, "getmouse", curses_getmouse, 0); @@ -5203,9 +5286,9 @@ Init_curses(void) rb_define_method(cWindow, "move", window_move, 2); rb_define_method(cWindow, "move_relative", window_move_relative, 2); rb_define_method(cWindow, "setpos", window_setpos, 2); -#if defined(USE_COLOR) && defined(HAVE_WCOLOR_SET) +#if defined(USE_COLOR) && (defined(HAVE_WCOLOR_SET) || defined(HAVE_WATTR_SET)) rb_define_method(cWindow, "color_set", window_color_set, 1); -#endif /* USE_COLOR && HAVE_WCOLOR_SET */ +#endif /* USE_COLOR && (HAVE_WCOLOR_SET || HAVE_WATTR_SET) */ rb_define_method(cWindow, "cury", window_cury, 0); rb_define_method(cWindow, "curx", window_curx, 0); rb_define_method(cWindow, "maxy", window_maxy, 0); diff --git a/ext/curses/extconf.rb b/ext/curses/extconf.rb index 2e1a6ebc..644ccf56 100644 --- a/ext/curses/extconf.rb +++ b/ext/curses/extconf.rb @@ -119,7 +119,9 @@ def exec_command(cmd) def_prog_mode reset_prog_mode timeout wtimeout nodelay init_color wcolor_set use_default_colors assume_default_colors newpad unget_wch get_wch wget_wch PDC_get_key_modifiers - chgat wchgat newterm) + chgat wchgat newterm + init_extended_color init_extended_pair extended_color_content + extended_pair_content reset_color_pairs wattr_set wattr_get) have_func(f) || (have_macro(f, curses) && $defs.push(format("-DHAVE_%s", f.upcase))) end convertible_int('chtype', [["#undef MOUSE_MOVED\n"]]+curses) or abort diff --git a/sample/colors.rb b/sample/colors.rb index b9a82aea..bfb393c6 100755 --- a/sample/colors.rb +++ b/sample/colors.rb @@ -5,6 +5,7 @@ # The TERM environment variable should be set to xterm-256color etc. to # use 256 colors. Curses.colors returns the color numbers of the terminal. +# With ncurses 6+ extended color support, color_pairs may exceed 256. begin init_screen @@ -14,14 +15,24 @@ else start_color - addstr "This Terminal supports #{colors} colors.\n" - - Curses.colors.times { |i| - Curses.init_pair(i, i, 0) - attrset(color_pair(i)) + extended = Curses.support_extended_colors? + addstr "This Terminal supports #{colors} colors, #{color_pairs} pairs" + addstr extended ? " (extended).\n" : ".\n" + + (extended ? [512, color_pairs].min : colors).times { |i| + next if i == 0 + Curses.init_pair(i, i % colors, (i / colors) % colors) + if extended + # color_pair() encodes into chtype and can't handle pairs > 255; + # use color_set on stdscr instead, which calls wattr_set internally. + stdscr.color_set(i) + else + attrset(color_pair(i)) + end addstr("#{i.to_s.rjust(3)} ") addstr("\n") if i == 15 || (i > 16 && (i - 15) % 36 == 0) } + stdscr.color_set(0) end getch