capabilities.go RawHistoryFolder
1// Package terminalcap owns pure terminal capability values and detection policy.
2package terminalcap
3
4import "strings"
5
6// ColorMode is the color output mode selected for one attachment.
7type ColorMode uint8
8
9const (
10 // TrueColor is the zero value so manually constructed attachments retain the
11 // historical renderer behavior; live Hello paths always use the detector.
12 TrueColor ColorMode = iota
13 Indexed256
14)
15
16// Source records how confidently a terminal capability was selected.
17type Source uint8
18
19const (
20 SourceUnknown Source = iota
21 SourceHeuristic
22 SourceDeclared
23)
24
25// Application identifies a known terminal application when its environment
26// provides a trustworthy origin signal.
27type Application uint8
28
29const (
30 ApplicationUnknown Application = iota
31 ApplicationKitty
32)
33
34// Capabilities describes the output features selected for one client attachment.
35type Capabilities struct {
36 ColorMode ColorMode
37 ColorSource Source
38 Application Application
39 KittyGraphics bool
40}
41
42// SupportsKittyGraphics reports whether the active outer-terminal probe
43// accepted the Kitty graphics protocol. Environment detection never sets it.
44func (c Capabilities) SupportsKittyGraphics() bool { return c.KittyGraphics }
45
46// TrueColor reports whether this attachment can receive RGB ANSI output.
47func (c Capabilities) TrueColor() bool { return c.ColorMode == TrueColor }
48
49// Detect derives conservative attachment capabilities from a client environment.
50func Detect(env []string) Capabilities {
51 values := environmentValues(env)
52 term := strings.ToLower(strings.TrimSpace(values["TERM"]))
53 colorTerm := strings.ToLower(strings.TrimSpace(values["COLORTERM"]))
54 caps := Capabilities{ColorMode: Indexed256}
55
56 if values["KITTY_WINDOW_ID"] != "" || values["KITTY_PID"] != "" || values["KITTY_LISTEN_ON"] != "" {
57 caps.Application = ApplicationKitty
58 }
59
60 kittyIdentity := term == "xterm-kitty" && caps.Application == ApplicationKitty
61 switch colorTerm {
62 case "truecolor", "24bit":
63 caps.ColorMode = TrueColor
64 caps.ColorSource = SourceDeclared
65 }
66 if term == "xterm-direct" || strings.HasSuffix(term, "-direct") {
67 caps.ColorMode = TrueColor
68 caps.ColorSource = SourceDeclared
69 }
70 if kittyIdentity {
71 if caps.ColorSource == SourceUnknown {
72 caps.ColorSource = SourceHeuristic
73 }
74 caps.ColorMode = TrueColor
75 return caps
76 }
77 if caps.ColorSource == SourceDeclared {
78 return caps
79 }
80 if strings.Contains(term, "256color") || term == "dumb" {
81 caps.ColorSource = SourceDeclared
82 }
83 return caps
84}
85
86func environmentValues(env []string) map[string]string {
87 values := make(map[string]string, len(env))
88 for _, entry := range env {
89 key, value, ok := strings.Cut(entry, "=")
90 if ok {
91 values[key] = value
92 }
93 }
94 return values
95}
96
97// DetectTrueColor reports whether the supplied terminal environment advertises
98// direct color support. Explicit TERM/COLORTERM values override env entries.
99func DetectTrueColor(termEnv, colorTerm string, env []string) bool {
100 detectionEnv := make([]string, 0, len(env)+2)
101 detectionEnv = append(detectionEnv, env...)
102 detectionEnv = append(detectionEnv, "TERM="+termEnv, "COLORTERM="+colorTerm)
103 return Detect(detectionEnv).TrueColor()
104}