Skip to content

Commit d209de2

Browse files
gh-156138: Keep the color pair when a curses write restores the rendition
addstr(), addnstr(), insstr() and insnstr() saved the window rendition with getattrs() and put it back with wattrset(), whose A_COLOR field holds only pairs 0 to 255, so a window using a larger pair lost it. Save and restore the pair with wattr_get() and wattr_set() where they exist.
1 parent 83531fd commit d209de2

2 files changed

Lines changed: 98 additions & 16 deletions

File tree

Lib/test/test_curses.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,30 @@ def test_output_string_attr_restored(self):
910910
func(0, 0, *args, curses.A_BOLD)
911911
self.assertEqual(win.getattrs(), curses.A_UNDERLINE)
912912

913+
@requires_colors
914+
@requires_curses_window_meth('color_set')
915+
@requires_curses_window_meth('attr_get')
916+
def test_output_string_pair_restored(self):
917+
# The rendition put back after a write includes the color pair, also
918+
# when it is larger than the A_COLOR field of a chtype holds.
919+
pairs = [7]
920+
if curses.has_extended_color_support() and curses.COLOR_PAIRS > 300:
921+
pairs.append(300)
922+
win = curses.newwin(2, 10, 0, 0)
923+
for pair in pairs:
924+
curses.init_pair(pair, curses.COLOR_RED, curses.COLOR_BLACK)
925+
for func, args in [(win.addstr, ('x',)), (win.addnstr, ('x', 1)),
926+
(win.insstr, ('x',)), (win.insnstr, ('x', 1))]:
927+
with self.subTest(func.__qualname__, pair=pair):
928+
win.color_set(pair)
929+
func(0, 0, *args, curses.A_BOLD)
930+
self.assertEqual(win.attr_get()[1], pair)
931+
win.color_set(pair)
932+
# y=100 is outside the window, so the write fails.
933+
self.assertRaises(curses.error, func, 100, 0, *args,
934+
curses.A_BOLD)
935+
self.assertEqual(win.attr_get()[1], pair)
936+
913937
def test_add_string_behavior(self):
914938
# addstr() advances the cursor past the written text; addnstr()
915939
# writes at most n characters.

Modules/_cursesmodule.c

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2161,6 +2161,56 @@ curses_wattrset(PyCursesWindowObject *self, attr_t attr, const char *funcname)
21612161
return 0;
21622162
}
21632163

2164+
/* Read the rendition a write with an *attr* argument has to put back. The
2165+
color pair is read apart from the attributes because the A_COLOR field of a
2166+
chtype holds only pairs 0 to 255, while a window can use a larger one. */
2167+
static int
2168+
curses_wattr_save(PyCursesWindowObject *self, attr_t *attrs, int *pair,
2169+
const char *funcname)
2170+
{
2171+
#if defined(HAVE_CURSES_WATTR_GET) && defined(HAVE_CURSES_WATTR_SET)
2172+
int rtn;
2173+
#if _NCURSES_EXTENDED_COLOR_FUNCS
2174+
short legacy_pair;
2175+
rtn = wattr_get(self->win, attrs, &legacy_pair, pair);
2176+
#else
2177+
short spair;
2178+
rtn = wattr_get(self->win, attrs, &spair, NULL);
2179+
*pair = spair;
2180+
#endif
2181+
if (rtn == ERR) {
2182+
curses_window_set_error(self, "wattr_get", funcname);
2183+
return -1;
2184+
}
2185+
#else
2186+
*attrs = getattrs(self->win);
2187+
*pair = 0;
2188+
#endif
2189+
return 0;
2190+
}
2191+
2192+
/* Put the rendition back. The name of the curses function used is
2193+
_CURSES_WATTR_RESTORE_FUNC, for the caller to name it in an error. */
2194+
#if defined(HAVE_CURSES_WATTR_GET) && defined(HAVE_CURSES_WATTR_SET)
2195+
#define _CURSES_WATTR_RESTORE_FUNC "wattr_set"
2196+
#else
2197+
#define _CURSES_WATTR_RESTORE_FUNC "wattrset"
2198+
#endif
2199+
2200+
static int
2201+
curses_wattr_restore(PyCursesWindowObject *self, attr_t attrs, int pair)
2202+
{
2203+
#if defined(HAVE_CURSES_WATTR_GET) && defined(HAVE_CURSES_WATTR_SET)
2204+
#if _NCURSES_EXTENDED_COLOR_FUNCS
2205+
return wattr_set(self->win, attrs, 0, &pair);
2206+
#else
2207+
return wattr_set(self->win, attrs, (short)pair, NULL);
2208+
#endif
2209+
#else
2210+
return wattrset(self->win, attrs);
2211+
#endif
2212+
}
2213+
21642214
/*[clinic input]
21652215
_curses.window.addstr
21662216
@@ -2201,6 +2251,7 @@ _curses_window_addstr_impl(PyCursesWindowObject *self, int group_left_1,
22012251
wchar_t *wstr = NULL;
22022252
#endif
22032253
attr_t attr_old = A_NORMAL;
2254+
int pair_old = 0;
22042255
int use_xy = group_left_1, use_attr = group_right_1;
22052256
const char *funcname;
22062257

@@ -2225,8 +2276,9 @@ _curses_window_addstr_impl(PyCursesWindowObject *self, int group_left_1,
22252276
return NULL;
22262277
}
22272278
if (use_attr) {
2228-
attr_old = getattrs(self->win);
2229-
if (curses_wattrset(self, attr, "addstr") < 0) {
2279+
if (curses_wattr_save(self, &attr_old, &pair_old, "addstr") < 0 ||
2280+
curses_wattrset(self, attr, "addstr") < 0)
2281+
{
22302282
curses_release_wstr(strtype, wstr);
22312283
Py_XDECREF(bytesobj);
22322284
return NULL;
@@ -2263,10 +2315,10 @@ _curses_window_addstr_impl(PyCursesWindowObject *self, int group_left_1,
22632315
Py_DECREF(bytesobj);
22642316
}
22652317
if (use_attr) {
2266-
int attr_rtn = wattrset(self->win, attr_old);
2318+
int attr_rtn = curses_wattr_restore(self, attr_old, pair_old);
22672319
if (rtn != ERR) {
22682320
rtn = attr_rtn;
2269-
funcname = "wattrset";
2321+
funcname = _CURSES_WATTR_RESTORE_FUNC;
22702322
}
22712323
}
22722324
return curses_window_check_err(self, rtn, funcname, "addstr");
@@ -2315,6 +2367,7 @@ _curses_window_addnstr_impl(PyCursesWindowObject *self, int group_left_1,
23152367
wchar_t *wstr = NULL;
23162368
#endif
23172369
attr_t attr_old = A_NORMAL;
2370+
int pair_old = 0;
23182371
int use_xy = group_left_1, use_attr = group_right_1;
23192372
const char *funcname;
23202373

@@ -2339,8 +2392,9 @@ _curses_window_addnstr_impl(PyCursesWindowObject *self, int group_left_1,
23392392
return NULL;
23402393

23412394
if (use_attr) {
2342-
attr_old = getattrs(self->win);
2343-
if (curses_wattrset(self, attr, "addnstr") < 0) {
2395+
if (curses_wattr_save(self, &attr_old, &pair_old, "addnstr") < 0 ||
2396+
curses_wattrset(self, attr, "addnstr") < 0)
2397+
{
23442398
curses_release_wstr(strtype, wstr);
23452399
Py_XDECREF(bytesobj);
23462400
return NULL;
@@ -2373,10 +2427,10 @@ _curses_window_addnstr_impl(PyCursesWindowObject *self, int group_left_1,
23732427
Py_DECREF(bytesobj);
23742428
}
23752429
if (use_attr) {
2376-
int attr_rtn = wattrset(self->win, attr_old);
2430+
int attr_rtn = curses_wattr_restore(self, attr_old, pair_old);
23772431
if (rtn != ERR) {
23782432
rtn = attr_rtn;
2379-
funcname = "wattrset";
2433+
funcname = _CURSES_WATTR_RESTORE_FUNC;
23802434
}
23812435
}
23822436
return curses_window_check_err(self, rtn, funcname, "addnstr");
@@ -4035,6 +4089,7 @@ _curses_window_insstr_impl(PyCursesWindowObject *self, int group_left_1,
40354089
wchar_t *wstr = NULL;
40364090
#endif
40374091
attr_t attr_old = A_NORMAL;
4092+
int pair_old = 0;
40384093
int use_xy = group_left_1, use_attr = group_right_1;
40394094
const char *funcname;
40404095

@@ -4059,8 +4114,9 @@ _curses_window_insstr_impl(PyCursesWindowObject *self, int group_left_1,
40594114
return NULL;
40604115

40614116
if (use_attr) {
4062-
attr_old = getattrs(self->win);
4063-
if (curses_wattrset(self, attr, "insstr") < 0) {
4117+
if (curses_wattr_save(self, &attr_old, &pair_old, "insstr") < 0 ||
4118+
curses_wattrset(self, attr, "insstr") < 0)
4119+
{
40644120
curses_release_wstr(strtype, wstr);
40654121
Py_XDECREF(bytesobj);
40664122
return NULL;
@@ -4093,10 +4149,10 @@ _curses_window_insstr_impl(PyCursesWindowObject *self, int group_left_1,
40934149
Py_DECREF(bytesobj);
40944150
}
40954151
if (use_attr) {
4096-
int attr_rtn = wattrset(self->win, attr_old);
4152+
int attr_rtn = curses_wattr_restore(self, attr_old, pair_old);
40974153
if (rtn != ERR) {
40984154
rtn = attr_rtn;
4099-
funcname = "wattrset";
4155+
funcname = _CURSES_WATTR_RESTORE_FUNC;
41004156
}
41014157
}
41024158
return curses_window_check_err(self, rtn, funcname, "insstr");
@@ -4147,6 +4203,7 @@ _curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1,
41474203
wchar_t *wstr = NULL;
41484204
#endif
41494205
attr_t attr_old = A_NORMAL;
4206+
int pair_old = 0;
41504207
int use_xy = group_left_1, use_attr = group_right_1;
41514208
const char *funcname;
41524209

@@ -4171,8 +4228,9 @@ _curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1,
41714228
return NULL;
41724229

41734230
if (use_attr) {
4174-
attr_old = getattrs(self->win);
4175-
if (curses_wattrset(self, attr, "insnstr") < 0) {
4231+
if (curses_wattr_save(self, &attr_old, &pair_old, "insnstr") < 0 ||
4232+
curses_wattrset(self, attr, "insnstr") < 0)
4233+
{
41764234
curses_release_wstr(strtype, wstr);
41774235
return NULL;
41784236
}
@@ -4204,10 +4262,10 @@ _curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1,
42044262
Py_DECREF(bytesobj);
42054263
}
42064264
if (use_attr) {
4207-
int attr_rtn = wattrset(self->win, attr_old);
4265+
int attr_rtn = curses_wattr_restore(self, attr_old, pair_old);
42084266
if (rtn != ERR) {
42094267
rtn = attr_rtn;
4210-
funcname = "wattrset";
4268+
funcname = _CURSES_WATTR_RESTORE_FUNC;
42114269
}
42124270
}
42134271
return curses_window_check_err(self, rtn, funcname, "insnstr");

0 commit comments

Comments
 (0)