From 417e61da004c2fd080f30b994030d15372af5d1b Mon Sep 17 00:00:00 2001 From: Nils Stinnesbeck Date: Wed, 21 Oct 2026 11:38:04 +0200 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW --- go.mod | 2 +- internal/ui/probe_unix.go | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 10ed047..7faf8e4 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/gdamore/tcell/v2 v2.13.10 github.com/rivo/tview v0.42.0 golang.org/x/image v0.47.0 + golang.org/x/sys v0.49.0 golang.org/x/term v0.37.0 ) @@ -13,6 +14,5 @@ require ( github.com/gdamore/encoding v1.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - golang.org/x/sys v0.49.0 // indirect golang.org/x/text v0.43.0 // indirect ) diff --git a/internal/ui/probe_unix.go b/internal/ui/probe_unix.go index a412352..56dff2e 100644 --- a/internal/ui/probe_unix.go +++ b/internal/ui/probe_unix.go @@ -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 - } } }