Skip to content

Commit 226dd86

Browse files
committed
feat: added image protocol detection
1 parent 84ae399 commit 226dd86

3 files changed

Lines changed: 133 additions & 5 deletions

File tree

examples/PrintCaps.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,8 @@ private static void printCaps(TermCaps caps) {
3838
System.out.println(" italic : " + caps.italic());
3939
System.out.println(" strikethrough : " + caps.strikethrough());
4040
System.out.println(" overline : " + caps.overline());
41+
System.out.println(" sixel : " + caps.sixel());
42+
System.out.println(" kittyGraphics : " + caps.kittyGraphics());
43+
System.out.println(" iterm2Images : " + caps.iterm2Images());
4144
}
4245
}

termcap/src/main/java/org/codejive/miniterm/termcap/TermCaps.java

Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ public final class TermCaps {
7676
/** Whether the terminal supports overline text. */
7777
private final boolean overline;
7878

79+
/** Whether the terminal supports sixel bitmap graphics (DCS sequences). */
80+
private final boolean sixel;
81+
82+
/** Whether the terminal supports the Kitty terminal graphics protocol (APC-based). */
83+
private final boolean kittyGraphics;
84+
85+
/** Whether the terminal supports the iTerm2 inline image protocol (OSC 1337). */
86+
private final boolean iterm2Images;
87+
7988
private TermCaps(Builder b) {
8089
this.colors = b.colors;
8190
this.altScreen = b.altScreen;
@@ -89,6 +98,9 @@ private TermCaps(Builder b) {
8998
this.italic = b.italic;
9099
this.strikethrough = b.strikethrough;
91100
this.overline = b.overline;
101+
this.sixel = b.sixel;
102+
this.kittyGraphics = b.kittyGraphics;
103+
this.iterm2Images = b.iterm2Images;
92104
}
93105

94106
// ── Capability getters ────────────────────────────────────────────────
@@ -153,6 +165,21 @@ public boolean overline() {
153165
return overline;
154166
}
155167

168+
/** Whether the terminal supports sixel bitmap graphics (DCS sequences). */
169+
public boolean sixel() {
170+
return sixel;
171+
}
172+
173+
/** Whether the terminal supports the Kitty terminal graphics protocol (APC-based). */
174+
public boolean kittyGraphics() {
175+
return kittyGraphics;
176+
}
177+
178+
/** Whether the terminal supports the iTerm2 inline image protocol (OSC 1337). */
179+
public boolean iterm2Images() {
180+
return iterm2Images;
181+
}
182+
156183
// ── Terminal control helpers ──────────────────────────────────────────
157184

158185
private static final String CSI = "\033[";
@@ -365,12 +392,51 @@ static void applyTerm(Builder b, String term) {
365392
b.colors(8).altScreen(true);
366393
} else if (term.startsWith("rxvt")) {
367394
b.colors(8).altScreen(true).mouse(true).unicode(true);
395+
} else if ("xterm-kitty".equals(term)) {
396+
// Ghostty and other terminals that set TERM=xterm-kitty support the Kitty graphics
397+
// protocol
398+
b.colors(16_777_216)
399+
.altScreen(true)
400+
.mouse(true)
401+
.settableTitle(true)
402+
.unicode(true)
403+
.kittyGraphics(true);
368404
} else if (term.startsWith("xterm") || term.startsWith("vte")) {
369405
b.colors(8).altScreen(true).mouse(true).settableTitle(true).unicode(true);
370406
} else if (term.startsWith("linux")) {
371407
b.colors(8).altScreen(true).unicode(true);
372408
} else if (term.startsWith("konsole")) {
373409
b.colors(8).altScreen(true).mouse(true).settableTitle(true).unicode(true);
410+
} else if (term.startsWith("foot")) {
411+
// foot (Wayland) 1.2.0+ supports sixel
412+
b.colors(256)
413+
.altScreen(true)
414+
.mouse(true)
415+
.bracketedPaste(true)
416+
.focusTracking(true)
417+
.unicode(true)
418+
.sixel(true);
419+
} else if (term.startsWith("mlterm")) {
420+
// mlterm 3.1.9+ supports sixel
421+
b.colors(256).altScreen(true).mouse(true).unicode(true).sixel(true);
422+
} else if (term.startsWith("mintty")) {
423+
// mintty 2.6.0+ supports sixel and the iTerm2 inline image protocol
424+
b.colors(256)
425+
.altScreen(true)
426+
.mouse(true)
427+
.bracketedPaste(true)
428+
.unicode(true)
429+
.sixel(true)
430+
.iterm2Images(true);
431+
} else if (term.startsWith("contour")) {
432+
// Contour supports sixel natively
433+
b.colors(16_777_216)
434+
.altScreen(true)
435+
.mouse(true)
436+
.bracketedPaste(true)
437+
.focusTracking(true)
438+
.unicode(true)
439+
.sixel(true);
374440
} else {
375441
// Unknown terminal — assume at least 8 colours
376442
b.colors(8);
@@ -419,7 +485,8 @@ static void applyEnvVars(Builder b, EnvSource env) {
419485
.unicode(true)
420486
.italic(true)
421487
.strikethrough(true)
422-
.overline(true);
488+
.overline(true)
489+
.sixel(true); // Windows Terminal 1.22+ (August 2024)
423490
return;
424491
}
425492

@@ -450,7 +517,10 @@ static void applyEnvVars(Builder b, EnvSource env) {
450517
.unicode(true)
451518
.italic(true)
452519
.strikethrough(true)
453-
.overline(true);
520+
.overline(true)
521+
.sixel(true) // iTerm2 3.3.0+
522+
.kittyGraphics(true) // iTerm2 added Kitty graphics in 2024
523+
.iterm2Images(true);
454524
break;
455525
case "WezTerm":
456526
b.colors(16_777_216)
@@ -464,7 +534,10 @@ static void applyEnvVars(Builder b, EnvSource env) {
464534
.unicode(true)
465535
.italic(true)
466536
.strikethrough(true)
467-
.overline(true);
537+
.overline(true)
538+
.sixel(true)
539+
.kittyGraphics(true)
540+
.iterm2Images(true);
468541
break;
469542
case "kitty":
470543
b.colors(16_777_216)
@@ -477,12 +550,29 @@ static void applyEnvVars(Builder b, EnvSource env) {
477550
.unicode(true)
478551
.italic(true)
479552
.strikethrough(true)
480-
.overline(true);
553+
.overline(true)
554+
.kittyGraphics(true); // kitty does not support sixel by design
481555
break;
482556
case "Apple_Terminal":
483557
if (b.colors < 256) b.colors(256);
484558
b.settableTitle(true).unicode(true);
485559
break;
560+
case "Ghostty":
561+
case "ghostty":
562+
b.colors(16_777_216)
563+
.altScreen(true)
564+
.mouse(true)
565+
.bracketedPaste(true)
566+
.focusTracking(true)
567+
.synchronizedOutput(true)
568+
.hyperlinks(true)
569+
.settableTitle(true)
570+
.unicode(true)
571+
.italic(true)
572+
.strikethrough(true)
573+
.overline(true)
574+
.kittyGraphics(true);
575+
break;
486576
default:
487577
break;
488578
}
@@ -495,6 +585,17 @@ static void applyEnvVars(Builder b, EnvSource env) {
495585
b.mouse(true);
496586
}
497587

588+
// KONSOLE_VERSION — KDE Konsole 22.04+ supports sixel and the Kitty graphics protocol.
589+
if (env.get("KONSOLE_VERSION") != null) {
590+
if (b.colors < 256) b.colors(256);
591+
b.sixel(true).kittyGraphics(true);
592+
}
593+
594+
// ZELLIJ — Zellij multiplexer 0.31.0+ passes sixel through to the underlying terminal.
595+
if (env.get("ZELLIJ") != null) {
596+
b.sixel(true);
597+
}
598+
498599
// Windows fallback: if no special env var fired but we are on Windows,
499600
// assume a VT-capable console host (Win10 1511+) with conservative caps.
500601
String os = System.getProperty("os.name", "").toLowerCase();
@@ -532,6 +633,9 @@ public static final class Builder {
532633
private boolean italic;
533634
private boolean strikethrough;
534635
private boolean overline;
636+
private boolean sixel;
637+
private boolean kittyGraphics;
638+
private boolean iterm2Images;
535639

536640
private Builder() {}
537641

@@ -548,6 +652,9 @@ private Builder(TermCaps base) {
548652
this.italic = base.italic;
549653
this.strikethrough = base.strikethrough;
550654
this.overline = base.overline;
655+
this.sixel = base.sixel;
656+
this.kittyGraphics = base.kittyGraphics;
657+
this.iterm2Images = base.iterm2Images;
551658
}
552659

553660
/** Returns the currently set colors value. */
@@ -615,6 +722,21 @@ public Builder overline(boolean v) {
615722
return this;
616723
}
617724

725+
public Builder sixel(boolean v) {
726+
this.sixel = v;
727+
return this;
728+
}
729+
730+
public Builder kittyGraphics(boolean v) {
731+
this.kittyGraphics = v;
732+
return this;
733+
}
734+
735+
public Builder iterm2Images(boolean v) {
736+
this.iterm2Images = v;
737+
return this;
738+
}
739+
618740
public TermCaps build() {
619741
return new TermCaps(this);
620742
}

termcap/src/main/java/org/codejive/miniterm/termcap/TermProber.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ public static boolean applyDA1(String seq, TermCaps.Builder b) {
178178
if (b.colors() < 8) b.colors(8);
179179
} else if (code == DA1_PARAM_256_COLOR) {
180180
if (b.colors() < 256) b.colors(256);
181+
} else if (code == DA1_PARAM_SIXEL) {
182+
b.sixel(true);
181183
}
182184
}
183185
return true;
@@ -222,7 +224,8 @@ private static void applyDa2(TermCaps.Builder b, int type, int version) {
222224
.unicode(true)
223225
.italic(true)
224226
.strikethrough(true)
225-
.overline(true);
227+
.overline(true)
228+
.kittyGraphics(true);
226229
break;
227230
case DA2_TYPE_XTERM:
228231
// xterm and iTerm2 both report type 0; distinguish via TERM_PROGRAM env var

0 commit comments

Comments
 (0)