From 70b9a4d257eb98853e2d6f812bccb1d1fba5c336 Mon Sep 17 00:00:00 2001 From: pranavchoudhary-tech Date: Tue, 18 Aug 2026 12:24:51 +0530 Subject: [PATCH 1/5] gh-155976: Refresh COLORS and COLOR_PAIRS in update_lines_cols --- Lib/test/test_curses.py | 36 ++++++++++++++++++++++++++++++++++++ Modules/_cursesmodule.c | 26 ++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 389cd043d6c0f3..914ef52af6439d 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -3324,6 +3324,42 @@ def test_set_term(self): self.assertIs(curses.set_term(a), b) # returns the previous one self.assertIs(curses.set_term(b), a) + def test_set_term_refreshes_lines_cols_colors(self): + s1 = self.make_pty() + s2 = self.make_pty() + a = curses.newterm('xterm', s1, s1) + if hasattr(curses, 'start_color'): + try: + curses.start_color() + except curses.error: + pass + b = curses.newterm('xterm-256color', s2, s2) + if hasattr(curses, 'start_color'): + try: + curses.start_color() + except curses.error: + pass + + curses.set_term(a) + lines_a, cols_a = curses.LINES, curses.COLS + colors_a = getattr(curses, 'COLORS', None) + + curses.set_term(b) + lines_b, cols_b = curses.LINES, curses.COLS + colors_b = getattr(curses, 'COLORS', None) + + self.assertEqual(curses.set_term(a), b) + self.assertEqual(curses.LINES, lines_a) + self.assertEqual(curses.COLS, cols_a) + if colors_a is not None: + self.assertEqual(curses.COLORS, colors_a) + + self.assertEqual(curses.set_term(b), a) + self.assertEqual(curses.LINES, lines_b) + self.assertEqual(curses.COLS, cols_b) + if colors_b is not None: + self.assertEqual(curses.COLORS, colors_b) + def test_window_keeps_screen_alive(self): # The standard window keeps its screen alive; dropping every other # reference and collecting must not invalidate the window. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 2ff15dd31d2180..18a7bb96a2c244 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -7913,6 +7913,32 @@ update_lines_cols(PyObject *private_module) goto error; } Py_DECREF(o); + + if (curses_start_color_called) { + o = PyLong_FromUnsignedLongLong((unsigned long long)COLORS); + if (o == NULL) { + goto error; + } + if (PyDict_SetItemString(exposed_module_dict, "COLORS", o) < 0) { + goto error; + } + if (PyDict_SetItemString(private_module_dict, "COLORS", o) < 0) { + goto error; + } + Py_DECREF(o); + + o = PyLong_FromUnsignedLongLong((unsigned long long)COLOR_PAIRS); + if (o == NULL) { + goto error; + } + if (PyDict_SetItemString(exposed_module_dict, "COLOR_PAIRS", o) < 0) { + goto error; + } + if (PyDict_SetItemString(private_module_dict, "COLOR_PAIRS", o) < 0) { + goto error; + } + Py_DECREF(o); + } Py_DECREF(exposed_module); return 1; From 713867a65be87655d432f16a59640fdb8bff9fe5 Mon Sep 17 00:00:00 2001 From: pranavchoudhary-tech Date: Tue, 18 Aug 2026 14:08:08 +0530 Subject: [PATCH 2/5] gh-155976: Add news entry --- .../next/C_API/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/C_API/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst diff --git a/Misc/NEWS.d/next/C_API/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst b/Misc/NEWS.d/next/C_API/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst new file mode 100644 index 00000000000000..4395f8c1503f1f --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst @@ -0,0 +1 @@ +Fix an issue where :func:`curses.set_term` did not update :const:`curses.COLORS` and :const:`curses.COLOR_PAIRS` when switching terminal screens. From 7b2b20cfefb4df6fee2137382361b436413e7e4e Mon Sep 17 00:00:00 2001 From: pranavchoudhary-tech Date: Wed, 19 Aug 2026 11:41:56 +0530 Subject: [PATCH 3/5] gh-155976: Move NEWS entry to Library category --- .../2026-08-18-12-30-00.gh-issue-155976.xyz123.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{C_API => Library}/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst (100%) diff --git a/Misc/NEWS.d/next/C_API/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst b/Misc/NEWS.d/next/Library/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst similarity index 100% rename from Misc/NEWS.d/next/C_API/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst rename to Misc/NEWS.d/next/Library/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst From 45a353a4601444b52098a68b7c3873801bea8dd0 Mon Sep 17 00:00:00 2001 From: pranavchoudhary-tech Date: Tue, 15 Sep 2026 15:33:21 +0530 Subject: [PATCH 4/5] gh-155976: Update LINES and COLS from stdscr in curses set_term --- Lib/test/test_curses.py | 56 ++++++++++++++++++++--------------------- Modules/_cursesmodule.c | 12 +++++++-- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 914ef52af6439d..e8e2f573332ccd 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -3325,40 +3325,40 @@ def test_set_term(self): self.assertIs(curses.set_term(b), a) def test_set_term_refreshes_lines_cols_colors(self): + from test.support import os_helper s1 = self.make_pty() s2 = self.make_pty() - a = curses.newterm('xterm', s1, s1) - if hasattr(curses, 'start_color'): - try: - curses.start_color() - except curses.error: - pass - b = curses.newterm('xterm-256color', s2, s2) - if hasattr(curses, 'start_color'): - try: - curses.start_color() - except curses.error: - pass + + with os_helper.EnvironmentVarGuard() as env: + env['LINES'] = '25' + env['COLUMNS'] = '80' + a = curses.newterm('xterm', s1, s1) + if hasattr(curses, 'start_color'): + try: + curses.start_color() + except curses.error: + pass + + env['LINES'] = '30' + env['COLUMNS'] = '100' + b = curses.newterm('xterm-256color', s2, s2) + if hasattr(curses, 'start_color'): + try: + curses.start_color() + except curses.error: + pass curses.set_term(a) - lines_a, cols_a = curses.LINES, curses.COLS - colors_a = getattr(curses, 'COLORS', None) - - curses.set_term(b) - lines_b, cols_b = curses.LINES, curses.COLS - colors_b = getattr(curses, 'COLORS', None) - - self.assertEqual(curses.set_term(a), b) - self.assertEqual(curses.LINES, lines_a) - self.assertEqual(curses.COLS, cols_a) - if colors_a is not None: - self.assertEqual(curses.COLORS, colors_a) + self.assertEqual((curses.LINES, curses.COLS), a.stdscr.getmaxyx()) + self.assertNotEqual((curses.LINES, curses.COLS), b.stdscr.getmaxyx()) + if hasattr(curses, 'COLORS'): + self.assertEqual(curses.COLORS, 8) self.assertEqual(curses.set_term(b), a) - self.assertEqual(curses.LINES, lines_b) - self.assertEqual(curses.COLS, cols_b) - if colors_b is not None: - self.assertEqual(curses.COLORS, colors_b) + self.assertEqual((curses.LINES, curses.COLS), b.stdscr.getmaxyx()) + self.assertNotEqual((curses.LINES, curses.COLS), a.stdscr.getmaxyx()) + if hasattr(curses, 'COLORS'): + self.assertEqual(curses.COLORS, 256) def test_window_keeps_screen_alive(self): # The standard window keeps its screen alive; dropping every other diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 18a7bb96a2c244..988b5bac7841ef 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -7890,7 +7890,11 @@ update_lines_cols(PyObject *private_module) goto error; } - o = PyLong_FromLong(LINES); + int lines = LINES; + if (stdscr != NULL) { + lines = getmaxy(stdscr); + } + o = PyLong_FromLong(lines); if (o == NULL) { goto error; } @@ -7902,7 +7906,11 @@ update_lines_cols(PyObject *private_module) } Py_DECREF(o); - o = PyLong_FromLong(COLS); + int cols = COLS; + if (stdscr != NULL) { + cols = getmaxx(stdscr); + } + o = PyLong_FromLong(cols); if (o == NULL) { goto error; } From 56edfc3723a7e35cb8b1ec48bf5d6231b491186f Mon Sep 17 00:00:00 2001 From: pranavchoudhary-tech Date: Wed, 16 Sep 2026 10:03:05 +0530 Subject: [PATCH 5/5] gh-155976: Address review comments on curses set_term --- Lib/test/test_curses.py | 34 +++++++++++-------- ...-08-18-12-30-00.gh-issue-155976.xyz123.rst | 1 - 2 files changed, 19 insertions(+), 16 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index e8e2f573332ccd..33f6dcb51d47e2 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -14,6 +14,7 @@ check_disallow_instantiation, MISSING_C_DOCSTRINGS, gc_collect, SHORT_TIMEOUT) from test.support.import_helper import import_module +from test.support import os_helper # Optionally test curses module. This currently requires that the # 'curses' resource be given on the regrtest command line using the -u @@ -3325,28 +3326,31 @@ def test_set_term(self): self.assertIs(curses.set_term(b), a) def test_set_term_refreshes_lines_cols_colors(self): - from test.support import os_helper s1 = self.make_pty() s2 = self.make_pty() - + with os_helper.EnvironmentVarGuard() as env: env['LINES'] = '25' env['COLUMNS'] = '80' - a = curses.newterm('xterm', s1, s1) - if hasattr(curses, 'start_color'): - try: - curses.start_color() - except curses.error: - pass - + try: + a = curses.newterm('xterm', s1, s1) + except curses.error: + self.skipTest('no xterm terminfo entry') + try: + curses.start_color() + except curses.error: + pass + env['LINES'] = '30' env['COLUMNS'] = '100' - b = curses.newterm('xterm-256color', s2, s2) - if hasattr(curses, 'start_color'): - try: - curses.start_color() - except curses.error: - pass + try: + b = curses.newterm('xterm-256color', s2, s2) + except curses.error: + self.skipTest('no xterm-256color terminfo entry') + try: + curses.start_color() + except curses.error: + pass curses.set_term(a) self.assertEqual((curses.LINES, curses.COLS), a.stdscr.getmaxyx()) diff --git a/Misc/NEWS.d/next/Library/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst b/Misc/NEWS.d/next/Library/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst deleted file mode 100644 index 4395f8c1503f1f..00000000000000 --- a/Misc/NEWS.d/next/Library/2026-08-18-12-30-00.gh-issue-155976.xyz123.rst +++ /dev/null @@ -1 +0,0 @@ -Fix an issue where :func:`curses.set_term` did not update :const:`curses.COLORS` and :const:`curses.COLOR_PAIRS` when switching terminal screens.