Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3242,5 +3242,32 @@ def test_color(self):
curses.slk_color(0)


@unittest.skipUnless(hasattr(curses, 'newterm'), 'requires curses.newterm()')
@unittest.skipIf(BROKEN_NEWTERM, 'ncurses < 6.5 mishandles repeated newterm()')
@unittest.skipIf(not term or term == 'unknown',
f"$TERM={term!r}, newterm() may not work")
@unittest.skipIf(sys.platform == "cygwin",
"cygwin's curses mostly just hangs")
class TermattrsTests(NewtermTestBase):
# A signed termattrs() only differs from an unsigned one on a terminal
# that advertises the topmost bit of the mask, which is A_ITALIC. Drive
# a terminal type that supports italics over a pseudo-terminal instead of
# trusting $TERM, which on CI is usually 'linux' and advertises none.

def test_termattrs_is_a_usable_mask(self):
s = self.make_pty()
try:
screen = curses.newterm('xterm-256color', s, s)
except curses.error:
self.skipTest('no xterm-256color terminfo entry')
attrs = curses.termattrs()
# An attribute mask is unsigned, whichever attributes the terminal
# happens to support.
self.assertGreaterEqual(attrs, 0)
# termattrs() exists to be passed back to the attribute functions,
# which reject a negative mask with OverflowError.
screen.stdscr.attrset(attrs)


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix :func:`curses.termattrs` returning a negative value on a terminal that
supports :const:`curses.A_ITALIC`. The attribute mask is now returned
unsigned, so it can be passed back to the attribute functions.
14 changes: 13 additions & 1 deletion Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5495,6 +5495,10 @@ static PyType_Spec PyCursesScreen_Type_spec = {
return curses_check_err(module, rtn, funcname, # X); \
}

/* Only for a function returning an int that may be ERR. A function returning
an attribute mask must not use this: the mask is unsigned and its topmost
bit is a valid attribute, so storing it in an int makes the result both
negative and, for an all-bits-set mask, indistinguishable from ERR. */
#define NoArgReturnIntFunctionBody(X) \
{ \
PyCursesStatefulInitialised(module); \
Expand Down Expand Up @@ -7899,7 +7903,15 @@ Return a logical OR of all video attributes supported by the terminal.
static PyObject *
_curses_termattrs_impl(PyObject *module)
/*[clinic end generated code: output=b06f437fce1b6fc4 input=0559882a04f84d1d]*/
NoArgReturnIntFunctionBody(termattrs)
{
PyCursesStatefulInitialised(module);

/* termattrs() returns a chtype mask, not a status, so there is no ERR to
check for. Go through chtype rather than int: A_ITALIC is the topmost
bit of a 32-bit mask, and sign-extending it would make the result
negative and unusable as an attribute. */
return PyLong_FromUnsignedLong((unsigned long)(chtype)termattrs());
}

#ifdef HAVE_CURSES_TERM_ATTRS
/*[clinic input]
Expand Down
Loading