Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
58 lines
1.5 KiB
Go
58 lines
1.5 KiB
Go
//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
|
|
}
|
|
}
|
|
}
|