From ae224bb566301d3602e9b090e37c1dcf5a48c914 Mon Sep 17 00:00:00 2001 From: Soumendra Ganguly <67527439+8vasu@users.noreply.github.com> Date: Thu, 26 Aug 2021 21:56:26 -0500 Subject: [PATCH] bpo-41818: Add termios.tcgetwinsize(), termios.tcsetwinsize(). (GH-23686) * Add termios.tcgetwinsize(), termios.tcsetwinsize(). Update docs. * Add TIOCGSIZE support to termios.tcgetwinsize() * Add TIOCSSIZE support to termios.tcsetwinsize() Authored-by: Soumendra Ganguly * termios.tcgetwinsize() and termios.tcsetwinsize() should return/accept two-item tuples instead of lists. * Refactor tcsetwinsize to share common code and accept any two item sequence, with overflow checking. Co-authored-by: Gregory P. Smith [Google] --- Doc/library/termios.rst | 16 ++ .../2020-12-08-01-08-58.bpo-41818.zO8vV7.rst | 1 + Modules/clinic/termios.c.h | 66 +++++++- Modules/termios.c | 158 ++++++++++++++++++ 4 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2020-12-08-01-08-58.bpo-41818.zO8vV7.rst diff --git a/Doc/library/termios.rst b/Doc/library/termios.rst index d75a87c55a4..8ced0bbbd77 100644 --- a/Doc/library/termios.rst +++ b/Doc/library/termios.rst @@ -74,6 +74,22 @@ The module defines the following functions: output, :const:`TCIOFF` to suspend input, or :const:`TCION` to restart input. +.. function:: tcgetwinsize(fd) + + Return a tuple ``(ws_row, ws_col)`` containing the tty window size for file + descriptor *fd*. Requires :const:`termios.TIOCGWINSZ` or + :const:`termios.TIOCGSIZE`. + + +.. function:: tcsetwinsize(fd, winsize) + + Set the tty window size for file descriptor *fd* from *winsize*, which is + a two-item tuple ``(ws_row, ws_col)`` like the one returned by + :func:`tcgetwinsize`. Requires at least one of the pairs + (:const:`termios.TIOCGWINSZ`, :const:`termios.TIOCSWINSZ`); + (:const:`termios.TIOCGSIZE`, :const:`termios.TIOCSSIZE`) to be defined. + + .. seealso:: Module :mod:`tty` diff --git a/Misc/NEWS.d/next/Library/2020-12-08-01-08-58.bpo-41818.zO8vV7.rst b/Misc/NEWS.d/next/Library/2020-12-08-01-08-58.bpo-41818.zO8vV7.rst new file mode 100644 index 00000000000..e8d6063d4b5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-12-08-01-08-58.bpo-41818.zO8vV7.rst @@ -0,0 +1 @@ +Soumendra Ganguly: add termios.tcgetwinsize(), termios.tcsetwinsize(). \ No newline at end of file diff --git a/Modules/clinic/termios.c.h b/Modules/clinic/termios.c.h index a45c1f64262..b4d179abbe4 100644 --- a/Modules/clinic/termios.c.h +++ b/Modules/clinic/termios.c.h @@ -222,4 +222,68 @@ termios_tcflow(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=a129179f1e2545cc input=a9049054013a1b77]*/ + +PyDoc_STRVAR(termios_tcgetwinsize__doc__, +"tcgetwinsize($module, fd, /)\n" +"--\n" +"\n" +"Get the tty winsize for file descriptor fd.\n" +"\n" +"Returns a tuple (ws_row, ws_col)."); + +#define TERMIOS_TCGETWINSIZE_METHODDEF \ + {"tcgetwinsize", (PyCFunction)termios_tcgetwinsize, METH_O, termios_tcgetwinsize__doc__}, + +static PyObject * +termios_tcgetwinsize_impl(PyObject *module, int fd); + +static PyObject * +termios_tcgetwinsize(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int fd; + + if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + goto exit; + } + return_value = termios_tcgetwinsize_impl(module, fd); + +exit: + return return_value; +} + +PyDoc_STRVAR(termios_tcsetwinsize__doc__, +"tcsetwinsize($module, fd, winsize, /)\n" +"--\n" +"\n" +"Set the tty winsize for file descriptor fd.\n" +"\n" +"The winsize to be set is taken from the winsize argument, which\n" +"is a two-item tuple (ws_row, ws_col) like the one returned by tcgetwinsize()."); + +#define TERMIOS_TCSETWINSIZE_METHODDEF \ + {"tcsetwinsize", (PyCFunction)(void(*)(void))termios_tcsetwinsize, METH_FASTCALL, termios_tcsetwinsize__doc__}, + +static PyObject * +termios_tcsetwinsize_impl(PyObject *module, int fd, PyObject *winsz); + +static PyObject * +termios_tcsetwinsize(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int fd; + PyObject *winsz; + + if (!_PyArg_CheckPositional("tcsetwinsize", nargs, 2, 2)) { + goto exit; + } + if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + goto exit; + } + winsz = args[1]; + return_value = termios_tcsetwinsize_impl(module, fd, winsz); + +exit: + return return_value; +} +/*[clinic end generated code: output=db808d31296f6643 input=a9049054013a1b77]*/ diff --git a/Modules/termios.c b/Modules/termios.c index fdfe589eb80..38573e25f51 100644 --- a/Modules/termios.c +++ b/Modules/termios.c @@ -315,6 +315,156 @@ termios_tcflow_impl(PyObject *module, int fd, int action) Py_RETURN_NONE; } +/*[clinic input] +termios.tcgetwinsize + + fd: fildes + / + +Get the tty winsize for file descriptor fd. + +Returns a tuple (ws_row, ws_col). +[clinic start generated code]*/ + +static PyObject * +termios_tcgetwinsize_impl(PyObject *module, int fd) +/*[clinic end generated code: output=31825977d5325fb6 input=5706c379d7fd984d]*/ +{ +#if defined(TIOCGWINSZ) + termiosmodulestate *state = PyModule_GetState(module); + struct winsize w; + if (ioctl(fd, TIOCGWINSZ, &w) == -1) { + return PyErr_SetFromErrno(state->TermiosError); + } + + PyObject *v; + if (!(v = PyTuple_New(2))) { + return NULL; + } + + PyTuple_SetItem(v, 0, PyLong_FromLong((long)w.ws_row)); + PyTuple_SetItem(v, 1, PyLong_FromLong((long)w.ws_col)); + if (PyErr_Occurred()) { + Py_DECREF(v); + return NULL; + } + return v; +#elif defined(TIOCGSIZE) + termiosmodulestate *state = PyModule_GetState(module); + struct ttysize s; + if (ioctl(fd, TIOCGSIZE, &s) == -1) { + return PyErr_SetFromErrno(state->TermiosError); + } + + PyObject *v; + if (!(v = PyTuple_New(2))) { + return NULL; + } + + PyTuple_SetItem(v, 0, PyLong_FromLong((long)s.ts_lines)); + PyTuple_SetItem(v, 1, PyLong_FromLong((long)s.ts_cols)); + if (PyErr_Occurred()) { + Py_DECREF(v); + return NULL; + } + return v; +#else + PyErr_SetString(PyExc_NotImplementedError, + "requires termios.TIOCGWINSZ and/or termios.TIOCGSIZE"); + return NULL; +#endif /* defined(TIOCGWINSZ) */ +} + +/*[clinic input] +termios.tcsetwinsize + + fd: fildes + winsize as winsz: object + / + +Set the tty winsize for file descriptor fd. + +The winsize to be set is taken from the winsize argument, which +is a two-item tuple (ws_row, ws_col) like the one returned by tcgetwinsize(). +[clinic start generated code]*/ + +static PyObject * +termios_tcsetwinsize_impl(PyObject *module, int fd, PyObject *winsz) +/*[clinic end generated code: output=2ac3c9bb6eda83e1 input=4a06424465b24aee]*/ +{ + if (!PySequence_Check(winsz) || PySequence_Size(winsz) != 2) { + PyErr_SetString(PyExc_TypeError, + "tcsetwinsize, arg 2: must be a two-item sequence"); + return NULL; + } + + PyObject *tmp_item; + long winsz_0, winsz_1; + tmp_item = PySequence_GetItem(winsz, 0); + winsz_0 = PyLong_AsLong(tmp_item); + if (winsz_0 == -1 && PyErr_Occurred()) { + Py_XDECREF(tmp_item); + return NULL; + } + Py_XDECREF(tmp_item); + tmp_item = PySequence_GetItem(winsz, 1); + winsz_1 = PyLong_AsLong(tmp_item); + if (winsz_1 == -1 && PyErr_Occurred()) { + Py_XDECREF(tmp_item); + return NULL; + } + Py_XDECREF(tmp_item); + + termiosmodulestate *state = PyModule_GetState(module); + +#if defined(TIOCGWINSZ) && defined(TIOCSWINSZ) + struct winsize w; + /* Get the old winsize because it might have + more fields such as xpixel, ypixel. */ + if (ioctl(fd, TIOCGWINSZ, &w) == -1) { + return PyErr_SetFromErrno(state->TermiosError); + } + + w.ws_row = (unsigned short) winsz_0; + w.ws_col = (unsigned short) winsz_1; + if ((((long)w.ws_row) != winsz_0) || (((long)w.ws_col) != winsz_1)) { + PyErr_SetString(PyExc_OverflowError, + "winsize value(s) out of range."); + return NULL; + } + + if (ioctl(fd, TIOCSWINSZ, &w) == -1) { + return PyErr_SetFromErrno(state->TermiosError); + } + + Py_RETURN_NONE; +#elif defined(TIOCGSIZE) && defined(TIOCSSIZE) + struct ttysize s; + /* Get the old ttysize because it might have more fields. */ + if (ioctl(fd, TIOCGSIZE, &s) == -1) { + return PyErr_SetFromErrno(state->TermiosError); + } + + s.ts_lines = (int) winsz_0; + s.ts_cols = (int) winsz_1; + if ((((long)s.ts_lines) != winsz_0) || (((long)s.ts_cols) != winsz_1)) { + PyErr_SetString(PyExc_OverflowError, + "winsize value(s) out of range."); + return NULL; + } + + if (ioctl(fd, TIOCSSIZE, &s) == -1) { + return PyErr_SetFromErrno(state->TermiosError); + } + + Py_RETURN_NONE; +#else + PyErr_SetString(PyExc_NotImplementedError, + "requires termios.TIOCGWINSZ, termios.TIOCSWINSZ and/or termios.TIOCGSIZE, termios.TIOCSSIZE"); + return NULL; +#endif /* defined(TIOCGWINSZ) && defined(TIOCSWINSZ) */ +} + static PyMethodDef termios_methods[] = { TERMIOS_TCGETATTR_METHODDEF @@ -323,6 +473,8 @@ static PyMethodDef termios_methods[] = TERMIOS_TCDRAIN_METHODDEF TERMIOS_TCFLUSH_METHODDEF TERMIOS_TCFLOW_METHODDEF + TERMIOS_TCGETWINSIZE_METHODDEF + TERMIOS_TCSETWINSIZE_METHODDEF {NULL, NULL} }; @@ -841,6 +993,9 @@ static struct constant { #ifdef TIOCGSERIAL {"TIOCGSERIAL", TIOCGSERIAL}, #endif +#ifdef TIOCGSIZE + {"TIOCGSIZE", TIOCGSIZE}, +#endif #ifdef TIOCGSOFTCAR {"TIOCGSOFTCAR", TIOCGSOFTCAR}, #endif @@ -973,6 +1128,9 @@ static struct constant { #ifdef TIOCSSERIAL {"TIOCSSERIAL", TIOCSSERIAL}, #endif +#ifdef TIOCSSIZE + {"TIOCSSIZE", TIOCSSIZE}, +#endif #ifdef TIOCSSOFTCAR {"TIOCSSOFTCAR", TIOCSSOFTCAR}, #endif