From 6eb0c6b6fe2279472467b5ae2db3046823707005 Mon Sep 17 00:00:00 2001 From: beefcarver Date: Fri, 30 Oct 2026 18:43:16 -0400 Subject: [PATCH] fix(artwork): ignore TMUX/ZELLIJ/STY values that mean 'not set' Some shells and wrappers export ZELLIJ=0 or TMUX=0 outside a multiplexer. A plain truthiness check treated that as 'inside tmux/zellij' and downgraded iTerm2/Kitty to the ANSI mosaic, so real artwork silently disappeared. Only meaningful values now count. --- somafm_tui/artwork.py | 20 ++++++++++++++++---- tests/test_artwork.py | 11 +++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/somafm_tui/artwork.py b/somafm_tui/artwork.py index 91870e6..ee4bf8d 100644 --- a/somafm_tui/artwork.py +++ b/somafm_tui/artwork.py @@ -30,6 +30,18 @@ _OFF_VALUES = {"0", "off", "no", "none", "false"} _PROTOCOL_VALUES = {"tgp", "iterm2"} +def _env_flag(env: Mapping[str, str], name: str) -> bool: + """True when an environment variable is set to a meaningful value. + + Multiplexers are inconsistent here (``TMUX`` is a path, ``STY`` a + session name) and some wrappers export ``ZELLIJ=0``/``TMUX=0`` when they + are *not* running, so a plain truthiness check would wrongly downgrade a + perfectly good iTerm2/Kitty to the ANSI mosaic. + """ + value = (env.get(name) or "").strip() + return bool(value) and value.lower() not in _OFF_VALUES + + def detect_protocol(environ: Mapping[str, str] | None = None) -> str | None: """Return ``"tgp"``, ``"iterm2"`` or ``None`` for the current terminal.""" env = os.environ if environ is None else environ @@ -57,7 +69,7 @@ def detect_protocol(environ: Mapping[str, str] | None = None) -> str | None: # Kitty inside tmux may not inherit TERM=xterm-kitty/KITTY_*. Ask tmux # what the outer terminal is; TGP is wrapped for tmux passthrough. - if env.get("TMUX") and shutil.which("tmux"): + if _env_flag(env, "TMUX") and shutil.which("tmux"): try: result = subprocess.run( ["tmux", "display-message", "-p", "#{client_termname}"], @@ -76,9 +88,9 @@ def detect_protocol(environ: Mapping[str, str] | None = None) -> str | None: # iTerm2/WezTerm inside tmux would need their own passthrough wrapper, so # default to the mosaic there. if ( - env.get("TMUX") - or env.get("ZELLIJ") - or env.get("STY") + _env_flag(env, "TMUX") + or _env_flag(env, "ZELLIJ") + or _env_flag(env, "STY") or term.startswith("screen") or term.startswith("tmux") or program in {"tmux", "screen"} diff --git a/tests/test_artwork.py b/tests/test_artwork.py index b29b523..a057504 100644 --- a/tests/test_artwork.py +++ b/tests/test_artwork.py @@ -62,3 +62,14 @@ async def test_fetch_validates_and_caches() -> None: fetched = await artwork.fetch(client, url) assert fetched == data assert artwork.load_cached(url) == data + + +def test_multiplexer_env_values_that_mean_no() -> None: + # Some wrappers export ZELLIJ=0 / TMUX=0 outside a multiplexer; that must + # not disable iTerm2 or Kitty graphics. + assert artwork.detect_protocol({"ZELLIJ": "0", "TERM_PROGRAM": "iTerm.app"}) == "iterm2" + assert artwork.detect_protocol({"ZELLIJ": "", "TERM_PROGRAM": "iTerm.app"}) == "iterm2" + assert artwork.detect_protocol({"TMUX": "0", "KITTY_WINDOW_ID": "1"}) == "tgp" + # ...while a real multiplexer still downgrades to the mosaic. + assert artwork.detect_protocol({"TMUX": "/tmp/tmux-501/default,1,0", "TERM_PROGRAM": "iTerm.app"}) is None + assert artwork.detect_protocol({"STY": "1234.pts-0.host", "TERM_PROGRAM": "iTerm.app"}) is None