terminal_probe.go RawHistoryFolder
1package client
2
3import (
4 "context"
5 "time"
6
7 "github.com/bnema/vev-vt/protocol/terminalquery"
8
9 "github.com/bnema/vev/internal/ports"
10)
11
12// terminalCapabilityProbeTimeout bounds the outer-terminal capability probe.
13// DA1 answers first on any terminal, so a real terminal finishes far sooner.
14const terminalCapabilityProbeTimeout = 150 * time.Millisecond
15
16// terminalCapabilities is what the outer terminal declared to the probe.
17type terminalCapabilities struct {
18 KittyGraphics bool
19 KittyKeyboard bool
20}
21
22// probeTerminalCapabilities performs one bounded probe of the outer terminal
23// through the lifecycle's only input pump. It must run before any other
24// consumer claims input. Probe responses are removed; every unrelated byte is
25// kept, in order, for the next consumer.
26func probeTerminalCapabilities(ctx context.Context, terminal ports.Terminal, clock ports.Clock, input *terminalInputPump) terminalCapabilities {
27 if input == nil || supervisorNil(terminal) || supervisorNil(clock) {
28 return terminalCapabilities{}
29 }
30 consumer, ok := input.tryClaim()
31 if !ok {
32 return terminalCapabilities{}
33 }
34 defer input.revoke(consumer)
35
36 query := terminalquery.KittyGraphicsQuery + terminalquery.KittyKeyboardQuery + terminalquery.DeviceAttributesQuery
37 if _, err := terminal.Out().Write([]byte(query)); err != nil {
38 return terminalCapabilities{}
39 }
40 if err := terminal.Flush(); err != nil {
41 return terminalCapabilities{}
42 }
43
44 probe := &terminalquery.Probe{}
45 var replay []byte
46 timer := clock.NewTimer(terminalCapabilityProbeTimeout)
47 defer timer.Stop()
48 // DA1 is the FIFO sentinel: once it arrives, the answers to the earlier
49 // queries are definitive.
50wait:
51 for !probe.DA1() {
52 select {
53 case <-ctx.Done():
54 break wait
55 case <-timer.C():
56 break wait
57 case <-input.readyFor(consumer):
58 result, ok := input.take(ctx, consumer)
59 if !ok {
60 if ctx.Err() != nil {
61 break wait
62 }
63 continue
64 }
65 replay = append(replay, probe.Feed(result.data)...)
66 input.ack(consumer)
67 if result.err != nil {
68 break wait
69 }
70 }
71 }
72 replay = append(replay, probe.Finish()...)
73 // Preserve before revoke so the next consumer reads these bytes before any
74 // later terminal read.
75 input.preserveResidual(consumer, replay)
76 if !probe.DA1() {
77 return terminalCapabilities{}
78 }
79 return terminalCapabilities{KittyGraphics: probe.KittyGraphics(), KittyKeyboard: probe.KittyKeyboard()}
80}