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 - } } }