Kitty graphics probe that works on macOS

Go cannot set a read deadline on a terminal device on macOS (kqueue does
not support them), so the probe failed at once and Ghostty and kitty got
block posters there. Wait for the answer with select(2) instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
This commit is contained in:
2026-10-21 11:38:04 +02:00
co-authored by Claude Opus 5.5
parent a71ca6e3fb
commit 417e61da00
2 changed files with 31 additions and 8 deletions
+30 -7
View File
@@ -4,9 +4,11 @@ package ui
import (
"bytes"
"errors"
"os"
"time"
"golang.org/x/sys/unix"
"golang.org/x/term"
)
@@ -36,13 +38,37 @@ func probeKittyGraphics(timeout time.Duration) bool {
// 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
}
// 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 {
n, err := tty.Read(buf)
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
@@ -50,8 +76,5 @@ func probeKittyGraphics(timeout time.Duration) bool {
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
}
}
}