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
24 changes: 24 additions & 0 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3083,6 +3083,30 @@ def test_set_term_prescr_screen(self):
# The current screen is unchanged, so refreshing it still works.
screen.stdscr.refresh()

@unittest.skipUnless(hasattr(curses, 'new_prescr'),
'requires curses.new_prescr()')
@unittest.skipUnless(hasattr(curses.screen, 'use'),
'requires curses.screen.use()')
def test_use_prescr_screen(self):
# use() makes its screen current for the callback, so a new_prescr()
# screen is current there without having a terminal. Operations that
# need one used to crash inside curses.
s = self.make_pty()
screen = curses.newterm('xterm', s, s)
prescr = curses.new_prescr()
for func in [
lambda scr: curses.doupdate(),
lambda scr: curses.newwin(3, 3),
lambda scr: screen.stdscr.refresh(),
lambda scr: screen.stdscr.getch(),
]:
with self.assertRaises(curses.error):
prescr.use(func)
# Affecting the state before initscr() is what such a screen is for.
prescr.use(lambda scr: curses.use_env(False))
# The current screen is unchanged.
screen.stdscr.refresh()

def test_initscr_after_newterm_keeps_screen_alive(self):
# initscr() called while a newterm() screen is current returns that
# screen's own standard window, so the window keeps the screen alive.
Expand Down
106 changes: 102 additions & 4 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,35 @@ _PyCursesStatefulCheckFunction(PyObject *module,
return 0;
}

/*
* Function to check that the current screen has a terminal, by testing
* stdscr, which a screen made by new_prescr() does not have. If an error
* occurs, a PyCursesError is set and this returns 0. Otherwise this
* returns 1.
*/
static int
_PyCursesStatefulCheckTerminal(PyObject *module)
{
if (stdscr != NULL) {
return 1;
}
cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, "the current screen has no terminal");
return 0;
}

/* Same as _PyCursesStatefulCheckTerminal() for a Window object. */
static int
curses_window_check_terminal(PyCursesWindowObject *win)
{
if (stdscr != NULL) {
return 1;
}
cursesmodule_state *state = get_cursesmodule_state_by_win(win);
PyErr_SetString(state->error, "the current screen has no terminal");
return 0;
}

#define PyCursesStatefulSetupTermCalled(MODULE) \
do { \
if (!_PyCursesStatefulCheckFunction(MODULE, \
Expand All @@ -370,7 +399,8 @@ _PyCursesStatefulCheckFunction(PyObject *module,
do { \
if (!_PyCursesStatefulCheckFunction(MODULE, \
curses_initscr_called, \
"initscr")) \
"initscr") \
|| !_PyCursesStatefulCheckTerminal(MODULE)) \
{ \
return 0; \
} \
Expand Down Expand Up @@ -1793,6 +1823,42 @@ curses_window_put_cells(PyCursesWindowObject *self, PyObject *obj,
Py_RETURN_NONE; \
}

/* Same as Window_OneArgNoReturnVoidFunction() for a function that needs
a terminal. */
#define Window_OneArgNoReturnVoidTerminalFunction(X, TYPE, PARSESTR) \
static PyObject * PyCursesWindow_ ## X \
(PyObject *op, PyObject *args) \
{ \
TYPE arg1; \
if (!PyArg_ParseTuple(args, PARSESTR, &arg1)) { \
return NULL; \
} \
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op); \
if (!curses_window_check_terminal(self)) { \
return NULL; \
} \
X(self->win, arg1); \
Py_RETURN_NONE; \
}

/* Same as Window_OneArgNoReturnFunction() for a function that needs
a terminal. */
#define Window_OneArgNoReturnTerminalFunction(X, TYPE, PARSESTR) \
static PyObject * PyCursesWindow_ ## X \
(PyObject *op, PyObject *args) \
{ \
TYPE arg1; \
if (!PyArg_ParseTuple(args, PARSESTR, &arg1)) { \
return NULL; \
} \
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op); \
if (!curses_window_check_terminal(self)) { \
return NULL; \
} \
int code = X(self->win, arg1); \
return curses_window_check_err(self, code, # X, NULL); \
}

#define Window_OneArgNoReturnFunction(X, TYPE, PARSESTR) \
static PyObject * PyCursesWindow_ ## X \
(PyObject *op, PyObject *args) \
Expand Down Expand Up @@ -1893,7 +1959,7 @@ Window_NoArgNoReturnVoidFunction(wclrtoeol)
Window_NoArgNoReturnVoidFunction(wclrtobot)
Window_NoArgNoReturnVoidFunction(wclear)

Window_OneArgNoReturnVoidFunction(idcok, int, "i;True(1) or False(0)")
Window_OneArgNoReturnVoidTerminalFunction(idcok, int, "i;True(1) or False(0)")
#ifdef HAVE_CURSES_IMMEDOK
Window_OneArgNoReturnVoidFunction(immedok, int, "i;True(1) or False(0)")
#endif
Expand All @@ -1905,8 +1971,8 @@ Window_NoArg2TupleReturnFunction(getmaxyx, int, "ii")
Window_NoArg2TupleReturnFunction(getparyx, int, "ii")

Window_OneArgNoReturnFunction(clearok, int, "i;True(1) or False(0)")
Window_OneArgNoReturnFunction(idlok, int, "i;True(1) or False(0)")
Window_OneArgNoReturnFunction(keypad, int, "i;True(1) or False(0)")
Window_OneArgNoReturnTerminalFunction(idlok, int, "i;True(1) or False(0)")
Window_OneArgNoReturnTerminalFunction(keypad, int, "i;True(1) or False(0)")
Window_OneArgNoReturnFunction(leaveok, int, "i;True(1) or False(0)")
Window_OneArgNoReturnFunction(nodelay, int, "i;True(1) or False(0)")
Window_OneArgNoReturnFunction(notimeout, int, "i;True(1) or False(0)")
Expand Down Expand Up @@ -2960,6 +3026,10 @@ _curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch,
int group_right_1, attr_t attr)
/*[clinic end generated code: output=ab03afa580aa6a2a input=cd74c42aadcc7e30]*/
{
if (!curses_window_check_terminal(self)) {
return NULL;
}

chtype ch_;
#ifdef HAVE_NCURSESW
cchar_t wch;
Expand Down Expand Up @@ -3206,6 +3276,10 @@ _curses_window_getch_impl(PyCursesWindowObject *self, int group_right_1,
{
int rtn;

if (!curses_window_check_terminal(self)) {
return NULL;
}

Py_BEGIN_ALLOW_THREADS
if (!group_right_1) {
rtn = wgetch(self->win);
Expand Down Expand Up @@ -3254,6 +3328,10 @@ _curses_window_getkey_impl(PyCursesWindowObject *self, int group_right_1,
{
int rtn;

if (!curses_window_check_terminal(self)) {
return NULL;
}

Py_BEGIN_ALLOW_THREADS
if (!group_right_1) {
rtn = wgetch(self->win);
Expand Down Expand Up @@ -3305,6 +3383,10 @@ _curses_window_get_wch_impl(PyCursesWindowObject *self, int group_right_1,
int y, int x)
/*[clinic end generated code: output=9f4f86e91fe50ef3 input=dd7e5367fb49dc48]*/
{
if (!curses_window_check_terminal(self)) {
return NULL;
}

#ifdef HAVE_NCURSESW
int ct;
wint_t rtn;
Expand Down Expand Up @@ -3454,6 +3536,10 @@ static PyObject *
PyCursesWindow_getstr(PyObject *op, PyObject *args)
{
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);

if (!curses_window_check_terminal(self)) {
return NULL;
}
return curses_window_getstr_bytes(self, args, "_curses.window.getstr");
}

Expand Down Expand Up @@ -3755,6 +3841,10 @@ static PyObject *
PyCursesWindow_get_wstr(PyObject *op, PyObject *args)
{
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);

if (!curses_window_check_terminal(self)) {
return NULL;
}
#ifdef HAVE_NCURSESW
int rtn, use_xy = 0, y = 0, x = 0;
unsigned int max_buf_size = 2048;
Expand Down Expand Up @@ -4271,6 +4361,10 @@ _curses_window_noutrefresh_impl(PyCursesWindowObject *self)
{
int rtn;

if (!curses_window_check_terminal(self)) {
return NULL;
}

#ifdef py_is_pad
if (py_is_pad(self->win)) {
if (!group_right_1) {
Expand Down Expand Up @@ -4501,6 +4595,10 @@ _curses_window_refresh_impl(PyCursesWindowObject *self, int group_right_1,
{
int rtn;

if (!curses_window_check_terminal(self)) {
return NULL;
}

#ifdef py_is_pad
if (py_is_pad(self->win)) {
if (!group_right_1) {
Expand Down
Loading