Skip to main content

mcraw_tui/
terminal.rs

1use std::sync::OnceLock;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub enum TerminalProtocol {
5    Sixel,
6    Kitty,
7    TextFallback,
8}
9
10static PROTOCOL: OnceLock<TerminalProtocol> = OnceLock::new();
11
12pub fn init(p: TerminalProtocol) {
13    _ = PROTOCOL.set(p);
14}
15
16pub fn protocol() -> TerminalProtocol {
17    *PROTOCOL.get().unwrap_or(&TerminalProtocol::TextFallback)
18}
19
20pub fn protocol_name(p: TerminalProtocol) -> &'static str {
21    match p {
22        TerminalProtocol::Sixel => "sixel",
23        TerminalProtocol::Kitty => "kitty",
24        TerminalProtocol::TextFallback => "text-fallback",
25    }
26}
27
28/// Detect the terminal image protocol from environment variables.
29///
30/// Kitty protocol (24-bit RGBA, no palette limits) is checked first and
31/// preferred. Sixel (palette-based, DEC-originated) is the fallback for
32/// terminals that don't support Kitty. Both encoders exist and work.
33///
34/// As of mid-2026 these are the terminals per platform:
35///
36/// | Platform  | Kitty                          | Sixel                     |
37/// |-----------|--------------------------------|---------------------------|
38/// | Linux     | kitty, Ghostty, Konsole,       | foot, contour, mlterm,    |
39/// |           | WezTerm (via APC passthrough)  | WezTerm, xterm+sixel      |
40/// | macOS     | kitty, Ghostty, WezTerm,       | WezTerm, Terminal.app,    |
41/// |           | iTerm2 3.6+, Terminal.app,     |                           |
42/// |           | Warp                           |                           |
43/// | Windows   | WezTerm, Windows Terminal 1.22+| WT 1.22+, mintty, ConEmu  |
44pub fn detect() -> TerminalProtocol {
45    // Manual override: MCRAW_FORCE_PROTOCOL=kitty | sixel | text
46    // Use when env-var auto-detection misses your terminal.
47    if let Ok(force) = std::env::var("MCRAW_FORCE_PROTOCOL") {
48        match force.to_lowercase().as_str() {
49            "kitty" => return TerminalProtocol::Kitty,
50            "sixel" => return TerminalProtocol::Sixel,
51            "text" => return TerminalProtocol::TextFallback,
52            _ => {}  // unrecognised value → fall through to auto-detect
53        }
54    }
55
56    let term = std::env::var("TERM").unwrap_or_default();
57    let term_program = std::env::var("TERM_PROGRAM").unwrap_or_default();
58
59    // ── Kitty Graphics Protocol ─────────────────────────────────────
60    // Checked first — highest quality: 24-bit RGBA, no palette limits.
61
62    // Kitty / Kitten
63    if std::env::var("KITTY_WINDOW_ID").is_ok()
64        || std::env::var("KITTY_PID").is_ok()
65        || term == "xterm-kitty"
66    {
67        return TerminalProtocol::Kitty;
68    }
69
70    // Ghostty
71    if term_program == "Ghostty" {
72        return TerminalProtocol::Kitty;
73    }
74
75    // VS Code integrated terminal (supported via xterm.js)
76    if term_program == "vscode" {
77        return TerminalProtocol::Kitty;
78    }
79
80    // WezTerm — supports both protocols on all platforms; prefer Kitty
81    if std::env::var("WEZTERM_EXECUTABLE").is_ok() {
82        return TerminalProtocol::Kitty;
83    }
84
85    // iTerm2 3.6+ (macOS) — adopted Kitty graphics protocol
86    if term_program == "iTerm.app" {
87        return TerminalProtocol::Kitty;
88    }
89
90    // Konsole (KDE) — supports both since 22.04; prefer Kitty
91    if std::env::var("KONSOLE_VERSION").is_ok() {
92        return TerminalProtocol::Kitty;
93    }
94
95    // Warp — sets WARP_IS_LOCAL_SHELL_SESSION in local shells
96    if term_program == "WarpTerminal" || std::env::var("WARP_IS_LOCAL_SHELL_SESSION").is_ok() {
97        return TerminalProtocol::Kitty;
98    }
99
100    // Rio — GPU-accelerated terminal with native Kitty support
101    if term_program == "Rio" {
102        return TerminalProtocol::Kitty;
103    }
104
105    // Terminal.app (macOS) — native Kitty graphics support (per terminfo.dev)
106    if term_program == "Apple_Terminal" {
107        return TerminalProtocol::Kitty;
108    }
109
110    // Tabby (formerly Terminus)
111    if term_program == "Tabby" {
112        return TerminalProtocol::Kitty;
113    }
114
115    // ── Sixel Graphics Protocol ─────────────────────────────────────
116    // Legacy DEC-originated protocol, palette-based (256-colour).
117
118    // TERM containing "sixel" — generic sixel-capable terminal
119    if term.contains("sixel") {
120        return TerminalProtocol::Sixel;
121    }
122
123    // foot (Wayland) — native sixel support
124    if term == "foot" || term.starts_with("foot-") {
125        return TerminalProtocol::Sixel;
126    }
127
128    // contour — sixel-native terminal emulator
129    if term == "contour" {
130        return TerminalProtocol::Sixel;
131    }
132
133    // mlterm — lightweight multi-lingual terminal with sixel
134    if term == "mlterm" {
135        return TerminalProtocol::Sixel;
136    }
137
138    // mintty (MSYS2 / Cygwin / Git Bash on Windows) — sixel since 3.7.
139    // On Windows, TERM=xterm-256color + MSYSTEM/MINGW_PREFIX is mintty.
140    if (std::env::var("MSYSTEM").is_ok() || std::env::var("MINGW_PREFIX").is_ok())
141        && (term.starts_with("xterm") || term == "cygwin")
142    {
143        return TerminalProtocol::Sixel;
144    }
145
146    // Windows Terminal 1.22+ — native sixel support through ConPTY.
147    // WT_SESSION is the canonical detection method on Windows
148    // (TERM_PROGRAM is not set by WT). Kitty protocol support is not
149    // confirmed on WT, so we stick with sixel which is known to work.
150    if std::env::var("WT_SESSION").is_ok() {
151        return TerminalProtocol::Sixel;
152    }
153
154    // ConEmu / ConsoleZ on Windows — supports sixel via DCS passthrough
155    if std::env::var("ConEmuPID").is_ok() || std::env::var("ConEmuHWND").is_ok() {
156        return TerminalProtocol::Sixel;
157    }
158
159    // ── Fallback ────────────────────────────────────────────────────
160    TerminalProtocol::TextFallback
161}