//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd
package tui
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/charmbracelet/x/term"
"golang.org/x/sys/unix"
)
// queryColors asks for the text and background colors and the palette entries the theme mixes from, then asks
// for the device attributes every terminal answers, so a terminal that
// ignores the color queries ends the read instead of stalling it.
func queryColors() (replyText string, restoreErr error) {
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return "", nil
}
defer tty.Close()
// Query failures use default colors; restoration failures must reach startup.
fd := tty.Fd()
state, err := term.MakeRaw(fd)
if err != nil {
return "", nil
}
defer func() {
if err := term.Restore(fd, state); err != nil {
restoreErr = fmt.Errorf("could not restore terminal settings after color detection: %w", err)
}
}()
var query strings.Builder
query.WriteString("\x1b]10;?\x1b\\\x1b]11;?\x1b\\")
for _, i := range queriedPalette {
query.WriteString("\x1b]4;")
query.WriteString(strconv.Itoa(i))
query.WriteString(";?\x1b\\")
}
query.WriteString("\x1b[c")
if _, err := tty.WriteString(query.String()); err != nil {
return "", nil
}
var reply strings.Builder
deadline := time.Now().Add(300 * time.Millisecond)
buf := make([]byte, 256)
for {
left := time.Until(deadline)
if left <= 0 {
break
}
tv := unix.NsecToTimeval(int64(left))
var ready unix.FdSet
ready.Set(int(fd))
n, err := unix.Select(int(fd)+1, &ready, nil, nil, &tv)
if err == unix.EINTR {
continue
}
if err != nil || n == 0 {
break
}
n, err = tty.Read(buf)
if err != nil {
break
}
reply.Write(buf[:n])
// The device-attributes answer every terminal sends closes the read.
if da := strings.Index(reply.String(), "\x1b[?"); da >= 0 && strings.Contains(reply.String()[da:], "c") {
break
}
}
return reply.String(), nil
}