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}