Skip to content
Merged
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
34 changes: 34 additions & 0 deletions Doc/library/curses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,40 @@ The module :mod:`!curses` defines the following functions:
Save the current state of the terminal modes in a buffer, usable by
:func:`resetty`.

.. function:: scr_dump(filename)

Write the current contents of the virtual screen to *filename*, which may be
a string or a :term:`path-like object`. The file can later be read by
:func:`scr_restore`, :func:`scr_init` or :func:`scr_set`. This is the
whole-screen counterpart of :meth:`window.putwin`.

.. versionadded:: next

.. function:: scr_restore(filename)

Set the virtual screen to the contents of *filename*, which must have been
written by :func:`scr_dump`. The next call to :func:`doupdate` or
:meth:`window.refresh` restores the screen to those contents.

.. versionadded:: next

.. function:: scr_init(filename)

Initialize the assumed contents of the terminal from *filename*, which must
have been written by :func:`scr_dump`. Use it when the terminal already
displays those contents, for example after another program has drawn the
screen, so that curses does not redraw what is already there.

.. versionadded:: next

.. function:: scr_set(filename)

Use *filename*, which must have been written by :func:`scr_dump`, as both
the virtual screen and the assumed terminal contents. This combines the
effects of :func:`scr_restore` and :func:`scr_init`.

.. versionadded:: next

.. function:: get_escdelay()

Retrieves the value set by :func:`set_escdelay`.
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ curses
returns a new window that is an independent duplicate of an existing one.
(Contributed by Serhiy Storchaka in :gh:`152258`.)

* Add the :mod:`curses` functions :func:`~curses.scr_dump`,
:func:`~curses.scr_restore`, :func:`~curses.scr_init` and
:func:`~curses.scr_set`, which dump the whole screen to a file and restore it.
(Contributed by Serhiy Storchaka in :gh:`152260`.)

gzip
----

Expand Down
37 changes: 37 additions & 0 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,43 @@ def test_putwin(self):
self.assertEqual(win.getmaxyx(), (5, 12))
self.assertEqual(win.instr(2, 0), b' Lorem ipsum')

def test_scr_dump(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test name seems to say that scr_dump() is tested, but in fact it tests 4 functions. You may add a comment to say it:

# Test scr_dump(), scr_restore(), scr_init() and scr_set()

# Test scr_dump(), scr_restore(), scr_init() and scr_set().
# scr_dump() writes the virtual screen to a named file; the other three
Comment thread
serhiy-storchaka marked this conversation as resolved.
# functions load it back. The dumped image is internal curses state,
# not a window, so the round-trip is checked by comparing dump files
# rather than reading cells.
stdscr = self.stdscr
stdscr.erase()
stdscr.addstr(0, 0, 'screen dump test')
stdscr.refresh()
with tempfile.TemporaryDirectory() as d:
dump = os.path.join(d, 'dump')
self.assertIsNone(curses.scr_dump(dump))
# Dumping the same screen again is deterministic.
dump2 = os.path.join(d, 'dump2')
curses.scr_dump(dump2)
with open(dump, 'rb') as f1, open(dump2, 'rb') as f2:
self.assertEqual(f1.read(), f2.read())
# scr_restore() reloads that virtual screen, so dumping it again
# reproduces the original file even after the screen has changed.
stdscr.erase()
stdscr.addstr(0, 0, 'something else')
stdscr.refresh()
self.assertIsNone(curses.scr_restore(dump))
restored = os.path.join(d, 'restored')
curses.scr_dump(restored)
with open(dump, 'rb') as f1, open(restored, 'rb') as f2:
self.assertEqual(f1.read(), f2.read())
# scr_init() and scr_set() accept a dump file and return None.
self.assertIsNone(curses.scr_init(dump))
self.assertIsNone(curses.scr_set(dump))
# A bytes (path-like) filename is accepted too.
curses.scr_dump(os.fsencode(dump))
# Restoring from a missing file is an error.
self.assertRaises(curses.error,
curses.scr_restore, os.path.join(d, 'nope'))

def test_borders_and_lines(self):
win = curses.newwin(5, 10, 5, 2)
win.border('|', '!', '-', '_',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add the :mod:`curses` functions :func:`~curses.scr_dump`,
:func:`~curses.scr_restore`, :func:`~curses.scr_init` and
:func:`~curses.scr_set`, which dump the whole screen to a file and restore it.
95 changes: 93 additions & 2 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
mcprint mvaddchnstr mvaddchstr mvcur mvinchnstr
mvinchstr mvinnstr mmvwaddchnstr mvwaddchstr
mvwinchnstr mvwinchstr mvwinnstr
restartterm ripoffline scr_dump
scr_init scr_restore scr_set scrl set_curterm setterm
restartterm ripoffline
scrl set_curterm setterm
tgetent tgetflag tgetnum tgetstr tgoto timeout tputs
vidattr vidputs waddchnstr waddchstr
wcolor_set winchnstr winchstr winnstr wmouse_trafo wscrl
Expand Down Expand Up @@ -5039,6 +5039,22 @@ static PyType_Spec PyCursesScreen_Type_spec = {
Py_RETURN_NONE; \
}

/*
* Function body for a module function that dumps or restores the whole screen
* through a file named by a single filesystem-path argument (filename).
*/
#define ScreenDumpFunctionBody(X) \
{ \
PyCursesStatefulInitialised(module); \
PyObject *bytes; \
if (!PyUnicode_FSConverter(filename, &bytes)) { \
return NULL; \
} \
int rtn = X(PyBytes_AS_STRING(bytes)); \
Py_DECREF(bytes); \
return curses_check_err(module, rtn, # X, NULL); \
}

/*********************************************************************
Global Functions
**********************************************************************/
Expand Down Expand Up @@ -5612,6 +5628,77 @@ _curses_getwin(PyObject *module, PyObject *file)
return res;
}

/*[clinic input]
_curses.scr_dump

filename: object
The file to write to.
/

Write the current contents of the virtual screen to a file.

The file can later be used to restore the screen with scr_restore(),
scr_init() or scr_set().
[clinic start generated code]*/

static PyObject *
_curses_scr_dump(PyObject *module, PyObject *filename)
/*[clinic end generated code: output=4425cfa505ac9577 input=358db4b370975345]*/
ScreenDumpFunctionBody(scr_dump)

/*[clinic input]
_curses.scr_restore

filename: object
The file to read from.
/

Set the virtual screen to the contents of a file made by scr_dump().

The next call to doupdate() or refresh() restores the screen to those
contents.
[clinic start generated code]*/

static PyObject *
_curses_scr_restore(PyObject *module, PyObject *filename)
/*[clinic end generated code: output=71d669fb560fa57b input=30b1d6b2c328dd55]*/
ScreenDumpFunctionBody(scr_restore)

/*[clinic input]
_curses.scr_init

filename: object
The file to read from.
/

Initialize the assumed terminal contents from a scr_dump() file.

Use it as what the terminal currently displays, for example after
another program has drawn the screen.
[clinic start generated code]*/

static PyObject *
_curses_scr_init(PyObject *module, PyObject *filename)
/*[clinic end generated code: output=2e861d381d710419 input=81c45e4702124ef6]*/
ScreenDumpFunctionBody(scr_init)

/*[clinic input]
_curses.scr_set

filename: object
The file to read from.
/

Use a scr_dump() file as both the virtual screen and the terminal.

This combines the effects of scr_restore() and scr_init().
[clinic start generated code]*/

static PyObject *
_curses_scr_set(PyObject *module, PyObject *filename)
/*[clinic end generated code: output=6056fdec12c5935f input=d248c20543cc289b]*/
ScreenDumpFunctionBody(scr_set)

/*[clinic input]
_curses.halfdelay

Expand Down Expand Up @@ -7715,6 +7802,10 @@ static PyMethodDef cursesmodule_methods[] = {
_CURSES_RESIZETERM_METHODDEF
_CURSES_RESIZE_TERM_METHODDEF
_CURSES_SAVETTY_METHODDEF
_CURSES_SCR_DUMP_METHODDEF
_CURSES_SCR_INIT_METHODDEF
_CURSES_SCR_RESTORE_METHODDEF
_CURSES_SCR_SET_METHODDEF
#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20081102
_CURSES_GET_ESCDELAY_METHODDEF
_CURSES_SET_ESCDELAY_METHODDEF
Expand Down
61 changes: 60 additions & 1 deletion Modules/clinic/_cursesmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading