Skip to content

gh-81163: Add support for extended color functions in ncurses 6.1 #17536

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
360bb19
bpo-36982, bpo-36630: replumb these curses module functions to suppor…
websurfer5 May 21, 2019
2be663b
bpo-36982, bpo-36630: refactor the curses color implementation functi…
websurfer5 May 22, 2019
6893bfe
bpo-36982, bpo-36630: _NCURSES_EXTENDED_COLOR_FUNCS is always defined…
websurfer5 May 23, 2019
fb6d81e
bpo-36982: add new function: curses.has_extended_color_support() to i…
websurfer5 May 24, 2019
3d48299
bpo-36982, bpo-36630: add test for curses.has_extended_color_support()
websurfer5 May 24, 2019
7337433
bpo-36982: reorganize new code to keep clinic happy
websurfer5 May 24, 2019
de55056
📜🤖 Added by blurb_it.
blurb-it[bot] May 25, 2019
a3827ec
bpo-36982: refactor color-pair functions to use clinic
websurfer5 May 29, 2019
7de2eb0
bpo-36982: Add back has_extended_color_support() method lost during r…
hpjansson Dec 9, 2019
b07f1bd
bpo-36982: Add back init_color() to clinic input
hpjansson Dec 9, 2019
ec1aecd
bpo-36982: Move the whatsnew entry forward to 3.9.
hpjansson May 5, 2020
9593685
bpo-36982: Improve error messages and fix code formatting
hpjansson May 5, 2020
e8f678b
bpo-36982: Move _curses_pair_content__doc__ back where it belongs
hpjansson May 5, 2020
2f8c724
bpo-36982: Use a plain integer for color pair argument
hpjansson May 5, 2020
fd4c215
bpo-36982: Revert rgb values to short; they are in the 0-1000 range
hpjansson May 5, 2020
cd9b643
bpo-36982: Improve error message specificity and formatting
hpjansson May 5, 2020
8c89c23
bpo-36982: Let Clinic remove redundant calls to PyFloat_Check()
hpjansson Jul 1, 2020
023d41c
bpo-36982: Move whatsnew entry forward from 3.9 to 3.10
hpjansson Jul 1, 2020
b777e93
bpo-36982: Use converters for color numbers and color pairs
hpjansson Jul 3, 2020
1242214
bpo-36982: Use converters for color components
hpjansson Jul 3, 2020
eb61ff5
bpo-36982: Throw ValueError instead of OverflowError if arg out of range
hpjansson Jul 3, 2020
3fe7b1a
bpo-36982: Improve error messages and tighten range checking
hpjansson Jul 3, 2020
333f54d
bpo-36982: Remove unused defines
hpjansson Jul 3, 2020
1749b1e
bpo-36982: Update accreditation
hpjansson Jul 3, 2020
f5a9873
bpo-36982: Properly check for overflow in converters
hpjansson Jul 16, 2020
9bad726
bpo-36982: Shortcut error checking in converters
hpjansson Jul 16, 2020
29efcf3
bpo-36982: Revert unrelated changes
hpjansson Jul 16, 2020
81ad399
bpo-36982: Define extended color macros in single block for clarity
hpjansson Jul 16, 2020
9ec614a
bpo-36982: Add ValueError tests for color and pair converters
hpjansson Jul 16, 2020
70fdaf3
bpo-36982: Add back documentation for has_extended_color_support()
hpjansson Jul 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Doc/library/curses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ The module :mod:`curses` defines the following functions:

Return ``True`` if the terminal can display colors; otherwise, return ``False``.

.. function:: has_extended_color_support()

Return ``True`` if the module supports extended colors; otherwise, return
``False``. Extended color support allows more than 256 color pairs for
terminals that support more than 16 colors (e.g. xterm-256color).

Extended color support requires ncurses version 6.1 or later.

.. versionadded:: 3.10

.. function:: has_ic()

Expand Down
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ New Modules
Improved Modules
================

curses
------

The extended color functions added in ncurses 6.1 will be used transparently
by :func:`curses.color_content`, :func:`curses.init_color`,
:func:`curses.init_pair`, and :func:`curses.pair_content`. A new function,
:func:`curses.has_extended_color_support`, indicates whether extended color
support is provided by the underlying ncurses library.
(Contributed by Jeffrey Kintscher and Hans Petter Jansson in :issue:`36982`.)

glob
----

Expand Down
16 changes: 15 additions & 1 deletion Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ def test_module_funcs(self):
curses.nocbreak, curses.noecho, curses.nonl,
curses.noqiflush, curses.noraw,
curses.reset_prog_mode, curses.termattrs,
curses.termname, curses.erasechar]:
curses.termname, curses.erasechar,
curses.has_extended_color_support]:
with self.subTest(func=func.__qualname__):
func()
if hasattr(curses, 'filter'):
Expand Down Expand Up @@ -292,6 +293,19 @@ def test_colors_funcs(self):
if hasattr(curses, 'use_default_colors'):
curses.use_default_colors()

self.assertRaises(ValueError, curses.color_content, -1)
self.assertRaises(ValueError, curses.color_content, curses.COLORS + 1)
self.assertRaises(ValueError, curses.color_content, -2**31 - 1)
self.assertRaises(ValueError, curses.color_content, 2**31)
self.assertRaises(ValueError, curses.color_content, -2**63 - 1)
self.assertRaises(ValueError, curses.color_content, 2**63 - 1)
self.assertRaises(ValueError, curses.pair_content, -1)
self.assertRaises(ValueError, curses.pair_content, curses.COLOR_PAIRS)
self.assertRaises(ValueError, curses.pair_content, -2**31 - 1)
self.assertRaises(ValueError, curses.pair_content, 2**31)
self.assertRaises(ValueError, curses.pair_content, -2**63 - 1)
self.assertRaises(ValueError, curses.pair_content, 2**63 - 1)

@requires_curses_func('keyname')
def test_keyname(self):
curses.keyname(13)
Expand Down
2 changes: 2 additions & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ Geert Jansen
Jack Jansen
Hans-Peter Jansen
Bill Janssen
Hans Petter Jansson
Jon Janzen
Thomas Jarosch
Juhana Jauhiainen
Expand Down Expand Up @@ -882,6 +883,7 @@ Sam Kimbrel
Tomohiko Kinebuchi
James King
W. Trevor King
Jeffrey Kintscher
Paul Kippes
Steve Kirsch
Sebastian Kirsche
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use ncurses extended color functions when available to support terminals with 256 colors, and add the new function :func:`curses.has_extended_color_support` to indicate whether extended color support is provided by the underlying ncurses library.
194 changes: 166 additions & 28 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,31 @@ typedef chtype attr_t; /* No attr_t type is available */
#define STRICT_SYSV_CURSES
#endif

#if defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS)
#define _NCURSES_EXTENDED_COLOR_FUNCS 1
#else
#define _NCURSES_EXTENDED_COLOR_FUNCS 0
#endif /* defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS) */

#if _NCURSES_EXTENDED_COLOR_FUNCS
#define _NCURSES_COLOR_VAL_TYPE int
#define _CURSES_INIT_COLOR_FUNC init_extended_color
#define _CURSES_INIT_PAIR_FUNC init_extended_pair
#define _COLOR_CONTENT_FUNC extended_color_content
#define _CURSES_PAIR_NUMBER_FUNC extended_pair_content
#else
#define _NCURSES_COLOR_VAL_TYPE short
#define _CURSES_INIT_COLOR_FUNC init_color
#define _CURSES_INIT_PAIR_FUNC init_pair
#define _COLOR_CONTENT_FUNC color_content
#define _CURSES_PAIR_NUMBER_FUNC pair_content
#endif /* _NCURSES_EXTENDED_COLOR_FUNCS */

#define _CURSES_FUNC_NAME_STR(s) #s

#define _CURSES_INIT_COLOR_FUNC_NAME _CURSES_FUNC_NAME_STR(_CURSES_INIT_COLOR_FUNC)
#define _CURSES_INIT_PAIR_FUNC_NAME _CURSES_FUNC_NAME_STR(_CURSES_INIT_PAIR_FUNC)

/*[clinic input]
module _curses
class _curses.window "PyCursesWindowObject *" "&PyCursesWindow_Type"
Expand Down Expand Up @@ -387,6 +412,104 @@ PyCurses_ConvertToString(PyCursesWindowObject *win, PyObject *obj,
return 0;
}

static int
color_converter(PyObject *arg, void *ptr)
{
long color_number;
int overflow;

color_number = PyLong_AsLongAndOverflow(arg, &overflow);
if (color_number == -1 && PyErr_Occurred())
return 0;

if (overflow > 0 || color_number > COLORS) {
PyErr_Format(PyExc_ValueError,
"Color number is greater than COLORS (%d).",
COLORS);
return 0;
}
else if (overflow < 0 || color_number < 0) {
PyErr_SetString(PyExc_ValueError,
"Color number is less than 0.");
return 0;
}

*(int *)ptr = (int)color_number;
return 1;
}

/*[python input]
class color_converter(CConverter):
type = 'int'
converter = 'color_converter'
[python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=4260d2b6e66b3709]*/

static int
pair_converter(PyObject *arg, void *ptr)
{
long pair_number;
int overflow;

pair_number = PyLong_AsLongAndOverflow(arg, &overflow);
if (pair_number == -1 && PyErr_Occurred())
return 0;

if (overflow > 0 || pair_number > COLOR_PAIRS - 1) {
PyErr_Format(PyExc_ValueError,
"Color pair is greater than COLOR_PAIRS-1 (%d).",
COLOR_PAIRS - 1);
return 0;
}
else if (overflow < 0 || pair_number < 1) {
PyErr_SetString(PyExc_ValueError,
"Color pair is less than 1.");
return 0;
}

*(int *)ptr = (int)pair_number;
return 1;
}

/*[python input]
class pair_converter(CConverter):
type = 'int'
converter = 'pair_converter'
[python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=1a918ae6a1b32af7]*/

static int
component_converter(PyObject *arg, void *ptr)
{
long component;
int overflow;

component = PyLong_AsLongAndOverflow(arg, &overflow);
if (component == -1 && PyErr_Occurred())
return 0;

if (overflow > 0 || component > 1000) {
PyErr_SetString(PyExc_ValueError,
"Color component is greater than 1000");
return 0;
}
else if (overflow < 0 || component < 0) {
PyErr_SetString(PyExc_ValueError,
"Color component is less than 0");
return 0;
}

*(short *)ptr = (short)component;
return 1;
}

/*[python input]
class component_converter(CConverter):
type = 'short'
converter = 'component_converter'
[python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=38e9be01d33927fb]*/

/* Function versions of the 3 functions for testing whether curses has been
initialised or not. */

Expand Down Expand Up @@ -2585,7 +2708,7 @@ NoArgOrFlagNoReturnFunctionBody(cbreak, flag)
/*[clinic input]
_curses.color_content

color_number: short
color_number: color
The number of the color (0 - COLORS).
/

Expand All @@ -2596,15 +2719,15 @@ which will be between 0 (no component) and 1000 (maximum amount of component).
[clinic start generated code]*/

static PyObject *
_curses_color_content_impl(PyObject *module, short color_number)
/*[clinic end generated code: output=cb15cf3120d4bfc1 input=5555abb1c11e11b7]*/
_curses_color_content_impl(PyObject *module, int color_number)
/*[clinic end generated code: output=17b466df7054e0de input=c10ef58f694b13ee]*/
{
short r,g,b;
_NCURSES_COLOR_VAL_TYPE r,g,b;

PyCursesInitialised;
PyCursesInitialisedColor;

if (color_content(color_number, &r, &g, &b) != ERR)
if (_COLOR_CONTENT_FUNC(color_number, &r, &g, &b) != ERR)
return Py_BuildValue("(iii)", r, g, b);
else {
PyErr_SetString(PyCursesError,
Expand All @@ -2616,7 +2739,7 @@ _curses_color_content_impl(PyObject *module, short color_number)
/*[clinic input]
_curses.color_pair

color_number: short
color_number: color
The number of the color (0 - COLORS).
/

Expand All @@ -2627,8 +2750,8 @@ other A_* attributes. pair_number() is the counterpart to this function.
[clinic start generated code]*/

static PyObject *
_curses_color_pair_impl(PyObject *module, short color_number)
/*[clinic end generated code: output=6a84cb6b29ecaf9a input=a9d3eb6f50e4dc12]*/
_curses_color_pair_impl(PyObject *module, int color_number)
/*[clinic end generated code: output=3fd752e8e24c93fb input=b049033819ab4ef5]*/
{
PyCursesInitialised;
PyCursesInitialisedColor;
Expand Down Expand Up @@ -3027,13 +3150,13 @@ _curses_has_key_impl(PyObject *module, int key)
/*[clinic input]
_curses.init_color

color_number: short
color_number: color
The number of the color to be changed (0 - COLORS).
r: short
r: component
Red component (0 - 1000).
g: short
g: component
Green component (0 - 1000).
b: short
b: component
Blue component (0 - 1000).
/

Expand All @@ -3045,24 +3168,24 @@ most terminals; it is active only if can_change_color() returns 1.
[clinic start generated code]*/

static PyObject *
_curses_init_color_impl(PyObject *module, short color_number, short r,
short g, short b)
/*[clinic end generated code: output=280236f5efe9776a input=f3a05bd38f619175]*/
_curses_init_color_impl(PyObject *module, int color_number, short r, short g,
short b)
/*[clinic end generated code: output=d7ed71b2d818cdf2 input=8a2fe94ca9204aa5]*/
{
PyCursesInitialised;
PyCursesInitialisedColor;

return PyCursesCheckERR(init_color(color_number, r, g, b), "init_color");
return PyCursesCheckERR(_CURSES_INIT_COLOR_FUNC(color_number, r, g, b), _CURSES_INIT_COLOR_FUNC_NAME);
}

/*[clinic input]
_curses.init_pair

pair_number: short
pair_number: pair
The number of the color-pair to be changed (1 - (COLOR_PAIRS-1)).
fg: short
fg: color
Foreground color number (0 - COLORS).
bg: short
bg: color
Background color number (0 - COLORS).
/

Expand All @@ -3073,14 +3196,13 @@ all occurrences of that color-pair are changed to the new definition.
[clinic start generated code]*/

static PyObject *
_curses_init_pair_impl(PyObject *module, short pair_number, short fg,
short bg)
/*[clinic end generated code: output=9c2ce39c22f376b6 input=c9f0b11b17a2ac6d]*/
_curses_init_pair_impl(PyObject *module, int pair_number, int fg, int bg)
/*[clinic end generated code: output=a0bba03d2bbc3ee6 input=b865583a18061c1f]*/
{
PyCursesInitialised;
PyCursesInitialisedColor;

return PyCursesCheckERR(init_pair(pair_number, fg, bg), "init_pair");
return PyCursesCheckERR(_CURSES_INIT_PAIR_FUNC(pair_number, fg, bg), _CURSES_INIT_PAIR_FUNC_NAME);
}

static PyObject *ModDict;
Expand Down Expand Up @@ -3697,23 +3819,23 @@ NoArgNoReturnFunctionBody(noraw)
/*[clinic input]
_curses.pair_content

pair_number: short
pair_number: pair
The number of the color pair (1 - (COLOR_PAIRS-1)).
/

Return a tuple (fg, bg) containing the colors for the requested color pair.
[clinic start generated code]*/

static PyObject *
_curses_pair_content_impl(PyObject *module, short pair_number)
/*[clinic end generated code: output=5a72aa1a28bbacf3 input=f4d7fec5643b976b]*/
_curses_pair_content_impl(PyObject *module, int pair_number)
/*[clinic end generated code: output=4a726dd0e6885f3f input=b42eacf8a4103852]*/
{
short f, b;
_NCURSES_COLOR_VAL_TYPE f, b;

PyCursesInitialised;
PyCursesInitialisedColor;

if (pair_content(pair_number, &f, &b)==ERR) {
if (_CURSES_PAIR_NUMBER_FUNC(pair_number, &f, &b)==ERR) {
PyErr_SetString(PyCursesError,
"Argument 1 was out of range. (1..COLOR_PAIRS-1)");
return NULL;
Expand Down Expand Up @@ -4450,6 +4572,21 @@ make_ncurses_version(void)

#endif /* NCURSES_VERSION */

/*[clinic input]
_curses.has_extended_color_support

Return True if the module supports extended colors; otherwise, return False.

Extended color support allows more than 256 color-pairs for terminals
that support more than 16 colors (e.g. xterm-256color).
[clinic start generated code]*/

static PyObject *
_curses_has_extended_color_support_impl(PyObject *module)
/*[clinic end generated code: output=68f1be2b57d92e22 input=4b905f046e35ee9f]*/
{
return PyBool_FromLong(_NCURSES_EXTENDED_COLOR_FUNCS);
}

/* List of functions defined in the module */

Expand All @@ -4476,6 +4613,7 @@ static PyMethodDef PyCurses_methods[] = {
_CURSES_GETSYX_METHODDEF
_CURSES_GETWIN_METHODDEF
_CURSES_HAS_COLORS_METHODDEF
_CURSES_HAS_EXTENDED_COLOR_SUPPORT_METHODDEF
_CURSES_HAS_IC_METHODDEF
_CURSES_HAS_IL_METHODDEF
_CURSES_HAS_KEY_METHODDEF
Expand Down
Loading