//go:build unix package ui import ( "bytes" "errors" "os" "time" "golang.org/x/sys/unix" "golang.org/x/term" ) // probeKittyGraphics 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) followed by a Primary Device Attributes request, // which every terminal answers. If the DA answer arrives without a graphics // answer before it, the protocol is not supported. Runs before the screen is // set up, on the controlling terminal. func probeKittyGraphics(timeout time.Duration) bool { tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return false } defer tty.Close() fd := int(tty.Fd()) state, err := term.MakeRaw(fd) if err != nil { return false } defer term.Restore(fd, state) const query = "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\" + "\x1b[c" if _, err := tty.WriteString(query); err != nil { return false } // 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 { return false // no answer in time: assume no support } 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 { return false } 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 { return false } got = append(got, buf[:n]...) if bytes.Contains(got, []byte("_Gi=31;OK")) { return true } if da := bytes.Index(got, []byte("\x1b[?")); da >= 0 && bytes.IndexByte(got[da:], 'c') >= 0 { return false // device attributes answered, graphics query not } } }