Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

output.py: support title setting via curses/terminfo #1343

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 2 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
122 changes: 85 additions & 37 deletions lib/portage/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,26 +268,67 @@ def nc_len(mystr):
_legal_terms_re = re.compile(
r"^(xterm|xterm-color|Eterm|aterm|rxvt|screen|kterm|rxvt-unicode|gnome|interix|tmux|st-256color|alacritty|konsole|foot)"
)
_disable_xtermTitle = None
_max_xtermTitle_len = 253
_xterm_title_supported = None
_max_xterm_title_len = 253
_title_init_seq = None
_title_finish_seq = None
_default_xterm_title = None


def init_xterm_titles():
global _xterm_title_supported
global _title_init_seq
global _title_finish_seq

if _xterm_title_supported is not None:
# already initialized
return

_xterm_title_supported = False

if not sys.__stderr__.isatty() or "TERM" not in os.environ:
return

try:
# by default check if we can dynamically query the proper title
# setting sequence via terminfo
curses = init_curses(sys.stderr)
gerstner-hub marked this conversation as resolved.
Show resolved Hide resolved

if curses is not None:
tsl = curses.tigetstr("tsl").decode()
fsl = curses.tigetstr("fsl").decode()
if tsl and fsl:
_xterm_title_supported = True
_title_init_seq = tsl
_title_finish_seq = fsl
return
except curses.error:
pass

if _legal_terms_re.match(os.environ["TERM"]) is not None:
# as a fallback use the well known xterm escape sequences for the hard
# coded suppoted terminal list
_xterm_title_supported = True
_title_init_seq = "\x1b]0;"
_title_finish_seq = "\x07"


def format_xterm_title(mystr):
if not _xterm_title_supported:
return mystr
return _title_init_seq + mystr + _title_finish_seq


def xtermTitle(mystr, raw=False):
global _disable_xtermTitle
if _disable_xtermTitle is None:
_disable_xtermTitle = not (
sys.__stderr__.isatty()
and "TERM" in os.environ
and _legal_terms_re.match(os.environ["TERM"]) is not None
)
init_xterm_titles()

if dotitles and not _disable_xtermTitle:
if dotitles and _xterm_title_supported:
# If the title string is too big then the terminal can
# misbehave. Therefore, truncate it if it's too big.
if len(mystr) > _max_xtermTitle_len:
mystr = mystr[:_max_xtermTitle_len]
if len(mystr) > _max_xterm_title_len:
mystr = mystr[:_max_xterm_title_len]
if not raw:
mystr = f"\x1b]0;{mystr}\x07"
mystr = format_xterm_title(mystr)

# avoid potential UnicodeEncodeError
mystr = _unicode_encode(
Expand All @@ -298,22 +339,15 @@ def xtermTitle(mystr, raw=False):
f.flush()


default_xterm_title = None


def xtermTitleReset():
global default_xterm_title
if default_xterm_title is None:
init_xterm_titles()
global _default_xterm_title
if _default_xterm_title is None:
prompt_command = os.environ.get("PROMPT_COMMAND")
if prompt_command == "":
default_xterm_title = ""
_default_xterm_title = ""
elif prompt_command is not None:
if (
dotitles
and "TERM" in os.environ
and _legal_terms_re.match(os.environ["TERM"]) is not None
and sys.__stderr__.isatty()
):
if dotitles and _xterm_title_supported:
from portage.process import find_binary, spawn

shell = os.environ.get("SHELL")
Expand All @@ -337,12 +371,16 @@ def xtermTitleReset():
home = os.environ.get("HOME", "")
if home != "" and pwd.startswith(home):
pwd = "~" + pwd[len(home) :]
default_xterm_title = "\x1b]0;{}@{}:{}\x07".format(
os.environ.get("LOGNAME", ""),
os.environ.get("HOSTNAME", "").split(".", 1)[0],
pwd,
_default_xterm_title = format_xterm_title(
"{}@{}:{}".format(
os.environ.get("LOGNAME", ""),
os.environ.get("HOSTNAME", "").split(".", 1)[0],
pwd,
)
)
xtermTitle(default_xterm_title, raw=True)
# since PROMPT_COMMAND can already contain escape sequences, output the
# title as a raw sequence without adding any additional sequences.
xtermTitle(_default_xterm_title, raw=True)


def notitles():
Expand Down Expand Up @@ -509,6 +547,19 @@ def new_styles(self, styles):
self.style_listener(styles)


def init_curses(fd):
try:
import curses

try:
curses.setupterm(term=os.environ.get("TERM", "unknown"), fd=fd.fileno())
return curses
except curses.error:
return None
except ImportError:
return None


def get_term_size(fd=None):
"""
Get the number of lines and columns of the tty that is connected to
Expand All @@ -522,15 +573,12 @@ def get_term_size(fd=None):
fd = sys.stdout
if not hasattr(fd, "isatty") or not fd.isatty():
return (0, 0)
try:
import curses

try:
curses.setupterm(term=os.environ.get("TERM", "unknown"), fd=fd.fileno())
try:
curses = init_curses(fd)
if curses is not None:
return curses.tigetnum("lines"), curses.tigetnum("cols")
except curses.error:
pass
except ImportError:
except curses.error:
pass

try:
Expand Down