As we known, Terminal has the ability to show image file. The typical protocals are iTerm2, sixel, kitty, etc. But there are also terminals which could only handle ASCII art pics(gnome-terminal, Terminal.app on Mac and the native tty on Ubuntu Server). Here is a good reference for the platform-capable protocal.
Thanks to yazi, I've written a script to detect different terminals and invoke proper image function tool.
show_company_logo() {
case $- in
*i*) ;;
*) return 0 ;;
esac
[ -t 1 ] || return 0
if [ -n "${COMPANY_LOGO_SHOWN:-}" ]; then
return 0
fi
export COMPANY_LOGO_SHOWN=1
local png="$HOME/.some.png"
local sixel="$HOME/.some.sixel"
local term="${TERM:-}"
local term_program="${TERM_PROGRAM:-}"
_logo_with_chafa() {
command -v chafa >/dev/null 2>&1 || return 1
[ -f "$png" ] || return 1
chafa --size=75x50 "$png"
}
_logo_with_imgcat() {
command -v imgcat >/dev/null 2>&1 || return 1
[ -f "$png" ] || return 1
imgcat --height 25 "$png"
}
_logo_with_sixel() {
[ -f "$sixel" ] || return 1
cat "$sixel"
}
_supports_sixel() {
[ -f "$HOME/.ifsixel" ] || return 1
command -v python3 >/dev/null 2>&1 || return 1
python3 "$HOME/.ifsixel" >/dev/null 2>&1
}
# Local Linux console on the server
if [ "$term" = "linux" ]; then
_logo_with_chafa && return 0
return 0
fi
# VS Code terminal over SSH
if [ "$term_program" = "vscode" ]; then
_logo_with_imgcat && return 0
return 0
fi
# Sixel-capable SSH terminal(Windows Terminal + SSH included), detected by active probe
if _supports_sixel; then
_logo_with_sixel && return 0
_logo_with_chafa && return 0
return 0
fi
# Generic fallback
_logo_with_chafa && return 0
return 0
}
show_company_logoHere is the py script to determine the ability of sixel, $HOME/.ifsixel
#!/usr/bin/env python3
import os
import sys
import time
import tty
import termios
import select
def read_response(fd, timeout=0.3):
data = b""
deadline = time.time() + timeout
while time.time() < deadline:
remaining = max(0, deadline - time.time())
r, _, _ = select.select([fd], [], [], remaining)
if not r:
break
chunk = os.read(fd, 4096)
if not chunk:
break
data += chunk
# Most DA replies end with 'c'
if b"c" in chunk:
break
return data
def supports_sixel_from_da(text: str) -> bool:
# Heuristic matcher for DA-style replies that may indicate sixel capability.
# This is intentionally conservative and can be refined with real samples.
if not text.startswith("\x1b[?") or not text.endswith("c"):
return False
# Example style: ESC [ ? ... c
body = text[3:-1]
parts = body.split(";")
# Common heuristic used in terminal capability discussions:
# presence of parameter 4 is often associated with sixel graphics support.
return "4" in parts
def main():
debug = "--debug" in sys.argv
try:
fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
except OSError:
return 1
old = termios.tcgetattr(fd)
try:
tty.setcbreak(fd)
# Flush pending input
while True:
r, _, _ = select.select([fd], [], [], 0)
if not r:
break
chunk = os.read(fd, 4096)
if not chunk:
break
# Primary device attributes
os.write(fd, b"\x1b[c")
data = read_response(fd, timeout=0.3)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
os.close(fd)
text = data.decode("ascii", errors="ignore")
if debug:
print(repr(data))
print(text)
return 0 if supports_sixel_from_da(text) else 1
if __name__ == "__main__":
raise SystemExit(main())Here is the hleper to output sixel sequence to a file
import os
import sys
import types
from io import BytesIO
from PIL import Image
# Provide a minimal termios stub for Windows so the sixel package can import.
if os.name == "nt" and "termios" not in sys.modules:
termios_stub = types.ModuleType("termios")
termios_stub.ECHO = 0
termios_stub.ICANON = 0
termios_stub.TCSANOW = 0
termios_stub.TCSAFLUSH = 0
termios_stub.tcgetattr = lambda fd: [0, 0, 0, 0, 0, 0]
termios_stub.tcsetattr = lambda fd, when, settings: None
sys.modules["termios"] = termios_stub
from sixel import SixelWriter
# Load an image using Pillow
img = Image.open(sys.argv[1])
# Resize for better terminal display (optional)
img.thumbnail((800, 600))
# Convert image to bytes
output = BytesIO()
img.save(output, format='PNG')
data = output.getvalue()
# Display using libsixel if available, otherwise fall back to pure Python implementation
libsixel_error = None
try:
from libsixel import sixel_output
try:
sixel_output(data)
sys.exit(0)
except Exception as e: # pragma: no cover - informative path
libsixel_error = e
except Exception as e: # pragma: no cover - informative path
libsixel_error = e
try:
buffer = BytesIO(data)
buffer.seek(0)
writer = SixelWriter()
writer.draw(buffer, output=sys.stdout)
except Exception as e:
print(f"Error displaying image: {e}")
if libsixel_error:
print(f"libsixel fallback error: {libsixel_error}")
print("Note: Your terminal may not support Sixel graphics")Some terminals set environments to identify itself.
[
("KITTY_WINDOW_ID", Kitty),
("KONSOLE_VERSION", Konsole),
("ITERM_SESSION_ID", Iterm2),
("WEZTERM_EXECUTABLE", WezTerm),
("GHOSTTY_RESOURCES_DIR", Ghostty),
("WT_Session", Microsoft),
("WARP_HONOR_PS1", Warp),
("VSCODE_INJECTION", VSCode),
("TABBY_CONFIG_DIRECTORY", Tabby),
]But not all of these env automatically pass through SSH session, so we need to send escape sequence and check the response.
The VT sequence provide a Query State code, DA(device attribute). The shell or cli program should send ESC [ 0 c to terminal and the terminal will respond some invisible characters. On official website it says that it will emit \x1b[?1;0c, indicating "VT101 with No Options". But in my experiment, Windows Terminal + SSH will respond \x1b[?61;4;6;7;14;21;22;23;24;28;32;42;52c, this is why the .issixel script check if there is character 4 in the response. And in native Windows Terminal, the response is \x1b[?61;1;6;7;21;22;23;24;28;32;42;52c. For comparison, gnome-terminal gives the \x1b[?65;1;9c, same as gnome-terminal + SSH. It's quite simple on mac Terminal.app with or without SSH, \x1b[?1;2c.

