//go:build unix package ui import ( "bytes" "os" "time" "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") if err := tty.SetReadDeadline(time.Now().Add(timeout)); err != nil { return false } var got []byte buf := make([]byte, 256) for { n, err := tty.Read(buf) 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 } if err != nil { return false // timeout or error: assume no support } } }