1// +build darwin dragonfly freebsd linux netbsd openbsd solaris 2 3package termenv 4 5import ( 6 "fmt" 7 "os" 8 "strconv" 9 "strings" 10 11 "golang.org/x/sys/unix" 12) 13 14func colorProfile() Profile { 15 term := os.Getenv("TERM") 16 colorTerm := os.Getenv("COLORTERM") 17 18 switch strings.ToLower(colorTerm) { 19 case "24bit": 20 fallthrough 21 case "truecolor": 22 if term == "screen" || !strings.HasPrefix(term, "screen") { 23 // enable TrueColor in tmux, but not for old-school screen 24 return TrueColor 25 } 26 case "yes": 27 fallthrough 28 case "true": 29 return ANSI256 30 } 31 32 if strings.Contains(term, "256color") { 33 return ANSI256 34 } 35 if strings.Contains(term, "color") { 36 return ANSI 37 } 38 39 return Ascii 40} 41 42func foregroundColor() Color { 43 s, err := termStatusReport(10) 44 if err == nil { 45 c, err := xTermColor(s) 46 if err == nil { 47 return c 48 } 49 } 50 51 colorFGBG := os.Getenv("COLORFGBG") 52 if strings.Contains(colorFGBG, ";") { 53 c := strings.Split(colorFGBG, ";") 54 i, err := strconv.Atoi(c[0]) 55 if err == nil { 56 return ANSIColor(i) 57 } 58 } 59 60 // default gray 61 return ANSIColor(7) 62} 63 64func backgroundColor() Color { 65 s, err := termStatusReport(11) 66 if err == nil { 67 c, err := xTermColor(s) 68 if err == nil { 69 return c 70 } 71 } 72 73 colorFGBG := os.Getenv("COLORFGBG") 74 if strings.Contains(colorFGBG, ";") { 75 c := strings.Split(colorFGBG, ";") 76 i, err := strconv.Atoi(c[1]) 77 if err == nil { 78 return ANSIColor(i) 79 } 80 } 81 82 // default black 83 return ANSIColor(0) 84} 85 86func readNextByte(f *os.File) (byte, error) { 87 var b [1]byte 88 n, err := f.Read(b[:]) 89 if err != nil { 90 return 0, err 91 } 92 93 if n == 0 { 94 panic("read returned no data") 95 } 96 97 return b[0], nil 98} 99 100// readNextResponse reads either an OSC response or a cursor position response: 101// * OSC response: "\x1b]11;rgb:1111/1111/1111\x1b\\" 102// * cursor position response: "\x1b[42;1R" 103func readNextResponse(fd *os.File) (response string, isOSC bool, err error) { 104 start, err := readNextByte(fd) 105 if err != nil { 106 return "", false, err 107 } 108 109 // if we encounter a backslash, this is a left-over from the previous OSC 110 // response, which can be terminated by an optional backslash 111 if start == '\\' { 112 start, err = readNextByte(fd) 113 if err != nil { 114 return "", false, err 115 } 116 } 117 118 // first byte must be ESC 119 if start != '\033' { 120 return "", false, ErrStatusReport 121 } 122 123 response += string(start) 124 125 // next byte is either '[' (cursor position response) or ']' (OSC response) 126 tpe, err := readNextByte(fd) 127 if err != nil { 128 return "", false, err 129 } 130 131 response += string(tpe) 132 133 var oscResponse bool 134 switch tpe { 135 case '[': 136 oscResponse = false 137 case ']': 138 oscResponse = true 139 default: 140 return "", false, ErrStatusReport 141 } 142 143 for { 144 b, err := readNextByte(os.Stdout) 145 if err != nil { 146 return "", false, err 147 } 148 149 response += string(b) 150 151 if oscResponse { 152 // OSC can be terminated by BEL (\a) or ST (ESC) 153 if b == '\a' || strings.HasSuffix(response, "\033") { 154 return response, true, nil 155 } 156 } else { 157 // cursor position response is terminated by 'R' 158 if b == 'R' { 159 return response, false, nil 160 } 161 } 162 163 // both responses have less than 25 bytes, so if we read more, that's an error 164 if len(response) > 25 { 165 break 166 } 167 } 168 169 return "", false, ErrStatusReport 170} 171 172func termStatusReport(sequence int) (string, error) { 173 // screen/tmux can't support OSC, because they can be connected to multiple 174 // terminals concurrently. 175 term := os.Getenv("TERM") 176 if strings.HasPrefix(term, "screen") { 177 return "", ErrStatusReport 178 } 179 180 t, err := unix.IoctlGetTermios(unix.Stdout, tcgetattr) 181 if err != nil { 182 return "", ErrStatusReport 183 } 184 defer unix.IoctlSetTermios(unix.Stdout, tcsetattr, t) 185 186 noecho := *t 187 noecho.Lflag = noecho.Lflag &^ unix.ECHO 188 noecho.Lflag = noecho.Lflag &^ unix.ICANON 189 if err := unix.IoctlSetTermios(unix.Stdout, tcsetattr, &noecho); err != nil { 190 return "", ErrStatusReport 191 } 192 193 // first, send OSC query, which is ignored by terminal which do not support it 194 fmt.Printf("\033]%d;?\033\\", sequence) 195 196 // then, query cursor position, should be supported by all terminals 197 fmt.Printf("\033[6n") 198 199 // read the next response 200 res, isOSC, err := readNextResponse(os.Stdout) 201 if err != nil { 202 return "", err 203 } 204 205 // if this is not OSC response, then the terminal does not support it 206 if !isOSC { 207 return "", ErrStatusReport 208 } 209 210 // read the cursor query response next and discard the result 211 _, _, err = readNextResponse(os.Stdout) 212 if err != nil { 213 return "", err 214 } 215 216 // fmt.Println("Rcvd", res[1:]) 217 return res, nil 218} 219