Skip to content

Instantly share code, notes, and snippets.

@s-zeid
Last active October 7, 2024 00:29
Show Gist options
  • Save s-zeid/c1115b4d9c7b0f5342a1a4ed51b8cd49 to your computer and use it in GitHub Desktop.
Save s-zeid/c1115b4d9c7b0f5342a1a4ed51b8cd49 to your computer and use it in GitHub Desktop.
Reset the terminal without clearing it.
#!/usr/bin/env python3
# vim: set fdm=marker:
# Copyright (c) 2024 S. Zeid.
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Reset the terminal without clearing it.
A file descriptor may be given as an argument. If omitted, it defaults
to standard output.
Does nothing if the given file descriptor is not a TTY, or if the platform
is unsupported.
"""
def main(argv: list[str]) -> int: #{{{1
if len(argv) > 1:
reset_tty(int(argv[1]))
else:
reset_tty()
return 0
def reset_tty(fd: int = 1) -> None: #{{{1
"""Resets the terminal without clearing it.
Does nothing if the given file descriptor (standard output by default)
is not a TTY, or if the platform is unsupported.
"""
# based on code from toybox, which is 0BSD licensed:
# <https://github.com/landley/toybox/blob/9cde583/LICENSE>
term = open(int(fd), "wb", closefd=False)
if not term.isatty():
return
try:
import termios
import tty
except ImportError:
return
tf = TFlags()
# <https://github.com/landley/toybox/blob/9cde583/toys/other/reset.c>
term.write(
#b"\x1bc" # reset terminal; this clears the screen, which we don't want
b"\x1b[?7h" # DEC private mode set enable wraparound sequence
)
term.flush()
# <https://github.com/landley/toybox/blob/9cde583/lib/tty.c#L109-L141>
tty.setraw(fd, termios.TCSANOW)
iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr(fd)
iflag |= tf.ICRNL|tf.IUTF8|tf.IXANY
oflag |= tf.OPOST|tf.ONLCR
cflag |= tf.CS8|tf.CREAD
lflag |= tf.ISIG|tf.ICANON|tf.ECHO|tf.ECHOE|tf.ECHOK|tf.ECHOCTL|tf.ECHOKE|tf.IEXTEN
termios.tcsetattr(fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
# <https://github.com/landley/toybox/blob/9cde583/lib/tty.c#L271>
term.write(
b"\x1b[?25h" # show cursor
b"\x1b[0m" # reset display attributes
#b"\x1b[999H" # move cursor to row 999, column 0; we don't want this
b"\x1b[K" # delete to EOL
)
term.flush()
class TFlags: #{{{1
"""A convenience class to access terminal flags, including some not supported by :mod:`termios`.
Attribute accesses for unknown terminal flags, or on unsupported platforms,
will return ``0``.
"""
import types as _types
_termios: _types.ModuleType | None
try:
import termios as _termios
except ImportError:
_termios = None
def __getattribute__(self, name):
if name.startswith("_"):
return object.__getattribute__(self, name)
elif name == "IUTF8":
return self._get_iutf8()
else:
try:
return self._termios.__getattribute__(name)
except AttributeError:
return 0
@staticmethod
def _get_iutf8(platform: str | None = None) -> int:
"""Returns the value of the IUTF8 termios input flag for the current or given platform.
If a platform is given, it should be a string that :data:`sys.platform` would be.
Returns 0 if this function does not know the value of IUTF8 for the platform.
"""
try:
# See if the Python stdlib has added support for IUTF8
if platform:
# ... but not if the caller gave a platform
raise AttributeError
import termios
return termios.IUTF8 # type: ignore
except (ImportError, AttributeError):
import sys
platform = platform or sys.platform
if platform.startswith("linux"):
# <https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/termbits.h>
return 0x4000
if platform.startswith("freebsd"):
# <https://github.com/freebsd/freebsd-src/blob/main/sys/sys/_termios.h>
return 0x00004000
if platform.startswith("darwin"):
# <https://github.com/apple-oss-distributions/xnu/blob/main/bsd/sys/termios.h>
return 0x00004000
return 0
if __name__ == "__main__": #{{{1
import sys
try:
sys.exit(main(sys.argv))
except KeyboardInterrupt:
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment