Skip to content

Commit fba514e

Browse files
hpjanssonwebsurfer5
authored andcommitted
bpo-36982: Add support for extended color functions in ncurses 6.1 (pythonGH-17536)
Co-authored-by: Jeffrey Kintscher <[email protected]>
1 parent 1090b51 commit fba514e

File tree

7 files changed

+258
-224
lines changed

7 files changed

+258
-224
lines changed

Doc/library/curses.rst

+9
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,15 @@ The module :mod:`curses` defines the following functions:
242242

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

245+
.. function:: has_extended_color_support()
246+
247+
Return ``True`` if the module supports extended colors; otherwise, return
248+
``False``. Extended color support allows more than 256 color pairs for
249+
terminals that support more than 16 colors (e.g. xterm-256color).
250+
251+
Extended color support requires ncurses version 6.1 or later.
252+
253+
.. versionadded:: 3.10
245254

246255
.. function:: has_ic()
247256

Doc/whatsnew/3.10.rst

+10
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ New Modules
103103
Improved Modules
104104
================
105105

106+
curses
107+
------
108+
109+
The extended color functions added in ncurses 6.1 will be used transparently
110+
by :func:`curses.color_content`, :func:`curses.init_color`,
111+
:func:`curses.init_pair`, and :func:`curses.pair_content`. A new function,
112+
:func:`curses.has_extended_color_support`, indicates whether extended color
113+
support is provided by the underlying ncurses library.
114+
(Contributed by Jeffrey Kintscher and Hans Petter Jansson in :issue:`36982`.)
115+
106116
glob
107117
----
108118

Lib/test/test_curses.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ def test_module_funcs(self):
232232
curses.nocbreak, curses.noecho, curses.nonl,
233233
curses.noqiflush, curses.noraw,
234234
curses.reset_prog_mode, curses.termattrs,
235-
curses.termname, curses.erasechar]:
235+
curses.termname, curses.erasechar,
236+
curses.has_extended_color_support]:
236237
with self.subTest(func=func.__qualname__):
237238
func()
238239
if hasattr(curses, 'filter'):
@@ -293,6 +294,19 @@ def test_colors_funcs(self):
293294
if hasattr(curses, 'use_default_colors'):
294295
curses.use_default_colors()
295296

297+
self.assertRaises(ValueError, curses.color_content, -1)
298+
self.assertRaises(ValueError, curses.color_content, curses.COLORS + 1)
299+
self.assertRaises(ValueError, curses.color_content, -2**31 - 1)
300+
self.assertRaises(ValueError, curses.color_content, 2**31)
301+
self.assertRaises(ValueError, curses.color_content, -2**63 - 1)
302+
self.assertRaises(ValueError, curses.color_content, 2**63 - 1)
303+
self.assertRaises(ValueError, curses.pair_content, -1)
304+
self.assertRaises(ValueError, curses.pair_content, curses.COLOR_PAIRS)
305+
self.assertRaises(ValueError, curses.pair_content, -2**31 - 1)
306+
self.assertRaises(ValueError, curses.pair_content, 2**31)
307+
self.assertRaises(ValueError, curses.pair_content, -2**63 - 1)
308+
self.assertRaises(ValueError, curses.pair_content, 2**63 - 1)
309+
296310
@requires_curses_func('keyname')
297311
def test_keyname(self):
298312
curses.keyname(13)

Misc/ACKS

+2
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ Geert Jansen
798798
Jack Jansen
799799
Hans-Peter Jansen
800800
Bill Janssen
801+
Hans Petter Jansson
801802
Jon Janzen
802803
Thomas Jarosch
803804
Juhana Jauhiainen
@@ -882,6 +883,7 @@ Sam Kimbrel
882883
Tomohiko Kinebuchi
883884
James King
884885
W. Trevor King
886+
Jeffrey Kintscher
885887
Paul Kippes
886888
Steve Kirsch
887889
Sebastian Kirsche
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

Modules/_cursesmodule.c

+166-28
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,31 @@ typedef chtype attr_t; /* No attr_t type is available */
134134
#define STRICT_SYSV_CURSES
135135
#endif
136136

137+
#if defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS)
138+
#define _NCURSES_EXTENDED_COLOR_FUNCS 1
139+
#else
140+
#define _NCURSES_EXTENDED_COLOR_FUNCS 0
141+
#endif /* defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS) */
142+
143+
#if _NCURSES_EXTENDED_COLOR_FUNCS
144+
#define _NCURSES_COLOR_VAL_TYPE int
145+
#define _CURSES_INIT_COLOR_FUNC init_extended_color
146+
#define _CURSES_INIT_PAIR_FUNC init_extended_pair
147+
#define _COLOR_CONTENT_FUNC extended_color_content
148+
#define _CURSES_PAIR_NUMBER_FUNC extended_pair_content
149+
#else
150+
#define _NCURSES_COLOR_VAL_TYPE short
151+
#define _CURSES_INIT_COLOR_FUNC init_color
152+
#define _CURSES_INIT_PAIR_FUNC init_pair
153+
#define _COLOR_CONTENT_FUNC color_content
154+
#define _CURSES_PAIR_NUMBER_FUNC pair_content
155+
#endif /* _NCURSES_EXTENDED_COLOR_FUNCS */
156+
157+
#define _CURSES_FUNC_NAME_STR(s) #s
158+
159+
#define _CURSES_INIT_COLOR_FUNC_NAME _CURSES_FUNC_NAME_STR(_CURSES_INIT_COLOR_FUNC)
160+
#define _CURSES_INIT_PAIR_FUNC_NAME _CURSES_FUNC_NAME_STR(_CURSES_INIT_PAIR_FUNC)
161+
137162
/*[clinic input]
138163
module _curses
139164
class _curses.window "PyCursesWindowObject *" "&PyCursesWindow_Type"
@@ -387,6 +412,104 @@ PyCurses_ConvertToString(PyCursesWindowObject *win, PyObject *obj,
387412
return 0;
388413
}
389414

415+
static int
416+
color_converter(PyObject *arg, void *ptr)
417+
{
418+
long color_number;
419+
int overflow;
420+
421+
color_number = PyLong_AsLongAndOverflow(arg, &overflow);
422+
if (color_number == -1 && PyErr_Occurred())
423+
return 0;
424+
425+
if (overflow > 0 || color_number > COLORS) {
426+
PyErr_Format(PyExc_ValueError,
427+
"Color number is greater than COLORS (%d).",
428+
COLORS);
429+
return 0;
430+
}
431+
else if (overflow < 0 || color_number < 0) {
432+
PyErr_SetString(PyExc_ValueError,
433+
"Color number is less than 0.");
434+
return 0;
435+
}
436+
437+
*(int *)ptr = (int)color_number;
438+
return 1;
439+
}
440+
441+
/*[python input]
442+
class color_converter(CConverter):
443+
type = 'int'
444+
converter = 'color_converter'
445+
[python start generated code]*/
446+
/*[python end generated code: output=da39a3ee5e6b4b0d input=4260d2b6e66b3709]*/
447+
448+
static int
449+
pair_converter(PyObject *arg, void *ptr)
450+
{
451+
long pair_number;
452+
int overflow;
453+
454+
pair_number = PyLong_AsLongAndOverflow(arg, &overflow);
455+
if (pair_number == -1 && PyErr_Occurred())
456+
return 0;
457+
458+
if (overflow > 0 || pair_number > COLOR_PAIRS - 1) {
459+
PyErr_Format(PyExc_ValueError,
460+
"Color pair is greater than COLOR_PAIRS-1 (%d).",
461+
COLOR_PAIRS - 1);
462+
return 0;
463+
}
464+
else if (overflow < 0 || pair_number < 1) {
465+
PyErr_SetString(PyExc_ValueError,
466+
"Color pair is less than 1.");
467+
return 0;
468+
}
469+
470+
*(int *)ptr = (int)pair_number;
471+
return 1;
472+
}
473+
474+
/*[python input]
475+
class pair_converter(CConverter):
476+
type = 'int'
477+
converter = 'pair_converter'
478+
[python start generated code]*/
479+
/*[python end generated code: output=da39a3ee5e6b4b0d input=1a918ae6a1b32af7]*/
480+
481+
static int
482+
component_converter(PyObject *arg, void *ptr)
483+
{
484+
long component;
485+
int overflow;
486+
487+
component = PyLong_AsLongAndOverflow(arg, &overflow);
488+
if (component == -1 && PyErr_Occurred())
489+
return 0;
490+
491+
if (overflow > 0 || component > 1000) {
492+
PyErr_SetString(PyExc_ValueError,
493+
"Color component is greater than 1000");
494+
return 0;
495+
}
496+
else if (overflow < 0 || component < 0) {
497+
PyErr_SetString(PyExc_ValueError,
498+
"Color component is less than 0");
499+
return 0;
500+
}
501+
502+
*(short *)ptr = (short)component;
503+
return 1;
504+
}
505+
506+
/*[python input]
507+
class component_converter(CConverter):
508+
type = 'short'
509+
converter = 'component_converter'
510+
[python start generated code]*/
511+
/*[python end generated code: output=da39a3ee5e6b4b0d input=38e9be01d33927fb]*/
512+
390513
/* Function versions of the 3 functions for testing whether curses has been
391514
initialised or not. */
392515

@@ -2585,7 +2708,7 @@ NoArgOrFlagNoReturnFunctionBody(cbreak, flag)
25852708
/*[clinic input]
25862709
_curses.color_content
25872710
2588-
color_number: short
2711+
color_number: color
25892712
The number of the color (0 - COLORS).
25902713
/
25912714
@@ -2596,15 +2719,15 @@ which will be between 0 (no component) and 1000 (maximum amount of component).
25962719
[clinic start generated code]*/
25972720

25982721
static PyObject *
2599-
_curses_color_content_impl(PyObject *module, short color_number)
2600-
/*[clinic end generated code: output=cb15cf3120d4bfc1 input=5555abb1c11e11b7]*/
2722+
_curses_color_content_impl(PyObject *module, int color_number)
2723+
/*[clinic end generated code: output=17b466df7054e0de input=c10ef58f694b13ee]*/
26012724
{
2602-
short r,g,b;
2725+
_NCURSES_COLOR_VAL_TYPE r,g,b;
26032726

26042727
PyCursesInitialised;
26052728
PyCursesInitialisedColor;
26062729

2607-
if (color_content(color_number, &r, &g, &b) != ERR)
2730+
if (_COLOR_CONTENT_FUNC(color_number, &r, &g, &b) != ERR)
26082731
return Py_BuildValue("(iii)", r, g, b);
26092732
else {
26102733
PyErr_SetString(PyCursesError,
@@ -2616,7 +2739,7 @@ _curses_color_content_impl(PyObject *module, short color_number)
26162739
/*[clinic input]
26172740
_curses.color_pair
26182741
2619-
color_number: short
2742+
color_number: color
26202743
The number of the color (0 - COLORS).
26212744
/
26222745
@@ -2627,8 +2750,8 @@ other A_* attributes. pair_number() is the counterpart to this function.
26272750
[clinic start generated code]*/
26282751

26292752
static PyObject *
2630-
_curses_color_pair_impl(PyObject *module, short color_number)
2631-
/*[clinic end generated code: output=6a84cb6b29ecaf9a input=a9d3eb6f50e4dc12]*/
2753+
_curses_color_pair_impl(PyObject *module, int color_number)
2754+
/*[clinic end generated code: output=3fd752e8e24c93fb input=b049033819ab4ef5]*/
26322755
{
26332756
PyCursesInitialised;
26342757
PyCursesInitialisedColor;
@@ -3027,13 +3150,13 @@ _curses_has_key_impl(PyObject *module, int key)
30273150
/*[clinic input]
30283151
_curses.init_color
30293152
3030-
color_number: short
3153+
color_number: color
30313154
The number of the color to be changed (0 - COLORS).
3032-
r: short
3155+
r: component
30333156
Red component (0 - 1000).
3034-
g: short
3157+
g: component
30353158
Green component (0 - 1000).
3036-
b: short
3159+
b: component
30373160
Blue component (0 - 1000).
30383161
/
30393162
@@ -3045,24 +3168,24 @@ most terminals; it is active only if can_change_color() returns 1.
30453168
[clinic start generated code]*/
30463169

30473170
static PyObject *
3048-
_curses_init_color_impl(PyObject *module, short color_number, short r,
3049-
short g, short b)
3050-
/*[clinic end generated code: output=280236f5efe9776a input=f3a05bd38f619175]*/
3171+
_curses_init_color_impl(PyObject *module, int color_number, short r, short g,
3172+
short b)
3173+
/*[clinic end generated code: output=d7ed71b2d818cdf2 input=8a2fe94ca9204aa5]*/
30513174
{
30523175
PyCursesInitialised;
30533176
PyCursesInitialisedColor;
30543177

3055-
return PyCursesCheckERR(init_color(color_number, r, g, b), "init_color");
3178+
return PyCursesCheckERR(_CURSES_INIT_COLOR_FUNC(color_number, r, g, b), _CURSES_INIT_COLOR_FUNC_NAME);
30563179
}
30573180

30583181
/*[clinic input]
30593182
_curses.init_pair
30603183
3061-
pair_number: short
3184+
pair_number: pair
30623185
The number of the color-pair to be changed (1 - (COLOR_PAIRS-1)).
3063-
fg: short
3186+
fg: color
30643187
Foreground color number (0 - COLORS).
3065-
bg: short
3188+
bg: color
30663189
Background color number (0 - COLORS).
30673190
/
30683191
@@ -3073,14 +3196,13 @@ all occurrences of that color-pair are changed to the new definition.
30733196
[clinic start generated code]*/
30743197

30753198
static PyObject *
3076-
_curses_init_pair_impl(PyObject *module, short pair_number, short fg,
3077-
short bg)
3078-
/*[clinic end generated code: output=9c2ce39c22f376b6 input=c9f0b11b17a2ac6d]*/
3199+
_curses_init_pair_impl(PyObject *module, int pair_number, int fg, int bg)
3200+
/*[clinic end generated code: output=a0bba03d2bbc3ee6 input=b865583a18061c1f]*/
30793201
{
30803202
PyCursesInitialised;
30813203
PyCursesInitialisedColor;
30823204

3083-
return PyCursesCheckERR(init_pair(pair_number, fg, bg), "init_pair");
3205+
return PyCursesCheckERR(_CURSES_INIT_PAIR_FUNC(pair_number, fg, bg), _CURSES_INIT_PAIR_FUNC_NAME);
30843206
}
30853207

30863208
static PyObject *ModDict;
@@ -3697,23 +3819,23 @@ NoArgNoReturnFunctionBody(noraw)
36973819
/*[clinic input]
36983820
_curses.pair_content
36993821
3700-
pair_number: short
3822+
pair_number: pair
37013823
The number of the color pair (1 - (COLOR_PAIRS-1)).
37023824
/
37033825
37043826
Return a tuple (fg, bg) containing the colors for the requested color pair.
37053827
[clinic start generated code]*/
37063828

37073829
static PyObject *
3708-
_curses_pair_content_impl(PyObject *module, short pair_number)
3709-
/*[clinic end generated code: output=5a72aa1a28bbacf3 input=f4d7fec5643b976b]*/
3830+
_curses_pair_content_impl(PyObject *module, int pair_number)
3831+
/*[clinic end generated code: output=4a726dd0e6885f3f input=b42eacf8a4103852]*/
37103832
{
3711-
short f, b;
3833+
_NCURSES_COLOR_VAL_TYPE f, b;
37123834

37133835
PyCursesInitialised;
37143836
PyCursesInitialisedColor;
37153837

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

44514573
#endif /* NCURSES_VERSION */
44524574

4575+
/*[clinic input]
4576+
_curses.has_extended_color_support
4577+
4578+
Return True if the module supports extended colors; otherwise, return False.
4579+
4580+
Extended color support allows more than 256 color-pairs for terminals
4581+
that support more than 16 colors (e.g. xterm-256color).
4582+
[clinic start generated code]*/
4583+
4584+
static PyObject *
4585+
_curses_has_extended_color_support_impl(PyObject *module)
4586+
/*[clinic end generated code: output=68f1be2b57d92e22 input=4b905f046e35ee9f]*/
4587+
{
4588+
return PyBool_FromLong(_NCURSES_EXTENDED_COLOR_FUNCS);
4589+
}
44534590

44544591
/* List of functions defined in the module */
44554592

@@ -4476,6 +4613,7 @@ static PyMethodDef PyCurses_methods[] = {
44764613
_CURSES_GETSYX_METHODDEF
44774614
_CURSES_GETWIN_METHODDEF
44784615
_CURSES_HAS_COLORS_METHODDEF
4616+
_CURSES_HAS_EXTENDED_COLOR_SUPPORT_METHODDEF
44794617
_CURSES_HAS_IC_METHODDEF
44804618
_CURSES_HAS_IL_METHODDEF
44814619
_CURSES_HAS_KEY_METHODDEF

0 commit comments

Comments
 (0)