1
2
3
4 package termenv
5
6 import (
7 "fmt"
8 "io"
9 "strconv"
10 "strings"
11 "time"
12
13 "golang.org/x/sys/unix"
14 )
15
16 const (
17
18 OSCTimeout = 5 * time.Second
19 )
20
21
22
23 func (o *Output) ColorProfile() Profile {
24 if !o.isTTY() {
25 return Ascii
26 }
27
28 if o.environ.Getenv("GOOGLE_CLOUD_SHELL") == "true" {
29 return TrueColor
30 }
31
32 term := o.environ.Getenv("TERM")
33 colorTerm := o.environ.Getenv("COLORTERM")
34
35 switch strings.ToLower(colorTerm) {
36 case "24bit":
37 fallthrough
38 case "truecolor":
39 if strings.HasPrefix(term, "screen") {
40
41 if o.environ.Getenv("TERM_PROGRAM") != "tmux" {
42 return ANSI256
43 }
44 }
45 return TrueColor
46 case "yes":
47 fallthrough
48 case "true":
49 return ANSI256
50 }
51
52 switch term {
53 case "xterm-kitty", "wezterm":
54 return TrueColor
55 case "linux":
56 return ANSI
57 }
58
59 if strings.Contains(term, "256color") {
60 return ANSI256
61 }
62 if strings.Contains(term, "color") {
63 return ANSI
64 }
65 if strings.Contains(term, "ansi") {
66 return ANSI
67 }
68
69 return Ascii
70 }
71
72 func (o Output) foregroundColor() Color {
73 s, err := o.termStatusReport(10)
74 if err == nil {
75 c, err := xTermColor(s)
76 if err == nil {
77 return c
78 }
79 }
80
81 colorFGBG := o.environ.Getenv("COLORFGBG")
82 if strings.Contains(colorFGBG, ";") {
83 c := strings.Split(colorFGBG, ";")
84 i, err := strconv.Atoi(c[0])
85 if err == nil {
86 return ANSIColor(i)
87 }
88 }
89
90
91 return ANSIColor(7)
92 }
93
94 func (o Output) backgroundColor() Color {
95 s, err := o.termStatusReport(11)
96 if err == nil {
97 c, err := xTermColor(s)
98 if err == nil {
99 return c
100 }
101 }
102
103 colorFGBG := o.environ.Getenv("COLORFGBG")
104 if strings.Contains(colorFGBG, ";") {
105 c := strings.Split(colorFGBG, ";")
106 i, err := strconv.Atoi(c[len(c)-1])
107 if err == nil {
108 return ANSIColor(i)
109 }
110 }
111
112
113 return ANSIColor(0)
114 }
115
116 func (o *Output) waitForData(timeout time.Duration) error {
117 fd := o.TTY().Fd()
118 tv := unix.NsecToTimeval(int64(timeout))
119 var readfds unix.FdSet
120 readfds.Set(int(fd))
121
122 for {
123 n, err := unix.Select(int(fd)+1, &readfds, nil, nil, &tv)
124 if err == unix.EINTR {
125 continue
126 }
127 if err != nil {
128 return err
129 }
130 if n == 0 {
131 return fmt.Errorf("timeout")
132 }
133
134 break
135 }
136
137 return nil
138 }
139
140 func (o *Output) readNextByte() (byte, error) {
141 if !o.unsafe {
142 if err := o.waitForData(OSCTimeout); err != nil {
143 return 0, err
144 }
145 }
146
147 var b [1]byte
148 n, err := o.TTY().Read(b[:])
149 if err != nil {
150 return 0, err
151 }
152
153 if n == 0 {
154 panic("read returned no data")
155 }
156
157 return b[0], nil
158 }
159
160
161
162
163 func (o *Output) readNextResponse() (response string, isOSC bool, err error) {
164 start, err := o.readNextByte()
165 if err != nil {
166 return "", false, err
167 }
168
169
170 for start != ESC {
171 start, err = o.readNextByte()
172 if err != nil {
173 return "", false, err
174 }
175 }
176
177 response += string(start)
178
179
180 tpe, err := o.readNextByte()
181 if err != nil {
182 return "", false, err
183 }
184
185 response += string(tpe)
186
187 var oscResponse bool
188 switch tpe {
189 case '[':
190 oscResponse = false
191 case ']':
192 oscResponse = true
193 default:
194 return "", false, ErrStatusReport
195 }
196
197 for {
198 b, err := o.readNextByte()
199 if err != nil {
200 return "", false, err
201 }
202
203 response += string(b)
204
205 if oscResponse {
206
207 if b == BEL || strings.HasSuffix(response, string(ESC)) {
208 return response, true, nil
209 }
210 } else {
211
212 if b == 'R' {
213 return response, false, nil
214 }
215 }
216
217
218 if len(response) > 25 {
219 break
220 }
221 }
222
223 return "", false, ErrStatusReport
224 }
225
226 func (o Output) termStatusReport(sequence int) (string, error) {
227
228
229 term := o.environ.Getenv("TERM")
230 if strings.HasPrefix(term, "screen") || strings.HasPrefix(term, "tmux") {
231 return "", ErrStatusReport
232 }
233
234 tty := o.TTY()
235 if tty == nil {
236 return "", ErrStatusReport
237 }
238
239 if !o.unsafe {
240 fd := int(tty.Fd())
241
242 if !isForeground(fd) {
243 return "", ErrStatusReport
244 }
245
246 t, err := unix.IoctlGetTermios(fd, tcgetattr)
247 if err != nil {
248 return "", fmt.Errorf("%s: %s", ErrStatusReport, err)
249 }
250 defer unix.IoctlSetTermios(fd, tcsetattr, t)
251
252 noecho := *t
253 noecho.Lflag = noecho.Lflag &^ unix.ECHO
254 noecho.Lflag = noecho.Lflag &^ unix.ICANON
255 if err := unix.IoctlSetTermios(fd, tcsetattr, &noecho); err != nil {
256 return "", fmt.Errorf("%s: %s", ErrStatusReport, err)
257 }
258 }
259
260
261 fmt.Fprintf(tty, OSC+"%d;?"+ST, sequence)
262
263
264 fmt.Fprintf(tty, CSI+"6n")
265
266
267 res, isOSC, err := o.readNextResponse()
268 if err != nil {
269 return "", fmt.Errorf("%s: %s", ErrStatusReport, err)
270 }
271
272
273 if !isOSC {
274 return "", ErrStatusReport
275 }
276
277
278 _, _, err = o.readNextResponse()
279 if err != nil {
280 return "", err
281 }
282
283
284 return res, nil
285 }
286
287
288
289
290
291 func EnableVirtualTerminalProcessing(_ io.Writer) (func() error, error) {
292 return func() error { return nil }, nil
293 }
294
View as plain text