Last active
October 7, 2024 00:29
-
-
Save s-zeid/c1115b4d9c7b0f5342a1a4ed51b8cd49 to your computer and use it in GitHub Desktop.
Reset the terminal without clearing it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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