Colors no longer come from the terminal's palette, whose color 0 many color themes set to a grey that is not their background. A theme sets tview's default styles and the color names used in texts, all chosen from entries 16-255 of the 256-color table so terminals without 24-bit color show them exactly. "auto" picks light or dark from the background color the terminal reports. Settable in the Config view, applied at the next start (widgets copy their colors when created). Replaces using the terminal's own background and text colors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
80 lines
2.2 KiB
Go
80 lines
2.2 KiB
Go
//go:build unix
|
|
|
|
package ui
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"os"
|
|
"time"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
// probeTerminal asks the terminal whether it understands the Kitty graphics
|
|
// protocol (a tiny query image, which a capable terminal answers with "OK" and
|
|
// does not show) and for its background color (OSC 11), which picks the
|
|
// theme. A Primary Device
|
|
// Attributes request follows, which every terminal answers, and answers come
|
|
// in order: once the DA answer is in, whatever was not answered before it is
|
|
// not supported. Runs before the screen is set up, on the controlling terminal.
|
|
func probeTerminal(timeout time.Duration) (info terminalInfo) {
|
|
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
|
|
if err != nil {
|
|
return info
|
|
}
|
|
defer tty.Close()
|
|
fd := int(tty.Fd())
|
|
state, err := term.MakeRaw(fd)
|
|
if err != nil {
|
|
return info
|
|
}
|
|
defer term.Restore(fd, state)
|
|
|
|
const query = "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\" + "\x1b]11;?\x1b\\" + "\x1b[c"
|
|
if _, err := tty.WriteString(query); err != nil {
|
|
return info
|
|
}
|
|
// A terminal that does not know APC sequences may print the query; erase it.
|
|
defer tty.WriteString("\r\x1b[2K")
|
|
|
|
// The wait uses select(2): a read deadline does not work on a terminal
|
|
// device on macOS (kqueue does not support them), which made the probe
|
|
// give up at once there.
|
|
deadline := time.Now().Add(timeout)
|
|
var got []byte
|
|
buf := make([]byte, 256)
|
|
for {
|
|
left := time.Until(deadline)
|
|
if left <= 0 {
|
|
break // no complete answer in time: keep what arrived
|
|
}
|
|
var fds unix.FdSet
|
|
fds.Set(fd)
|
|
tv := unix.NsecToTimeval(left.Nanoseconds())
|
|
n, err := unix.Select(fd+1, &fds, nil, nil, &tv)
|
|
if errors.Is(err, unix.EINTR) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
break
|
|
}
|
|
if n == 0 {
|
|
continue // timed out; the loop ends above
|
|
}
|
|
n, err = unix.Read(fd, buf)
|
|
if errors.Is(err, unix.EINTR) || errors.Is(err, unix.EAGAIN) {
|
|
continue
|
|
}
|
|
if err != nil || n <= 0 {
|
|
break
|
|
}
|
|
got = append(got, buf[:n]...)
|
|
if da := bytes.Index(got, []byte("\x1b[?")); da >= 0 && bytes.IndexByte(got[da:], 'c') >= 0 {
|
|
break // device attributes answered: everything else came before
|
|
}
|
|
}
|
|
return parseTerminalAnswers(got)
|
|
}
|