1package xturbo
2
3import (
4 "encoding/binary"
5 "fmt"
6 "os"
7 "time"
8
9 "golang.org/x/sys/unix"
10)
11
12// Core and extension requests root issues itself. Root does not import the
13// generated packages, so these few are encoded here; tests compare them with
14// the generated encoders and the request goldens.
15const (
16 opInternAtom = 16
17 opGetInputFocus = 43
18 opQueryExtension = 98
19 minorGetXIDRange = 1 // XC-MISC
20)
21
22func encodeGetInputFocus(b *Buf) Seq {
23 _, seq := b.Request(opGetInputFocus, 0, 0)
24 return seq
25}
26
27// encodeNamed encodes QueryExtension and InternAtom: CARD16 name_len, 2
28// pad bytes, then the name padded to 4.
29func encodeNamed(b *Buf, major, data uint8, name string) Seq {
30 if len(name) > 0xffff {
31 b.Fail(ErrBadLength)
32 return 0
33 }
34 p, seq := b.Request(major, data, pad4(4+len(name)))
35 if p == nil {
36 return 0
37 }
38 binary.LittleEndian.PutUint16(p, uint16(len(name)))
39 copy(p[4:], name)
40 return seq
41}
42
43func encodeQueryExtension(b *Buf, name string) Seq {
44 return encodeNamed(b, opQueryExtension, 0, name)
45}
46
47func encodeInternAtom(b *Buf, onlyIfExists bool, name string) Seq {
48 var data uint8
49 if onlyIfExists {
50 data = 1
51 }
52 return encodeNamed(b, opInternAtom, data, name)
53}
54
55// encodeExtRequest encodes a body-less extension request (BIG-REQUESTS
56// Enable, XC-MISC GetXIDRange). It fails with ErrExtMissing when id is absent.
57func encodeExtRequest(b *Buf, id ExtID, minor uint8) Seq {
58 major := b.Ext.Major(id)
59 if major == 0 {
60 b.Fail(ErrExtMissing)
61 return 0
62 }
63 _, seq := b.Request(major, minor, 0)
64 return seq
65}
66
67// Bounds of the blocking calls. Vars so tests can shorten them.
68var (
69 // connectTimeout bounds NewConn: setup handshake plus extension bootstrap.
70 connectTimeout = 10 * time.Second
71 // syncTimeout bounds one Sync, InternAtoms or blocking XID refill.
72 syncTimeout = 5 * time.Second
73)
74
75// deadlineError wraps os.ErrDeadlineExceeded for a blocking call that timed
76// out waiting for the server.
77func deadlineError(what string) error {
78 return fmt.Errorf("xturbo: %s: no answer from the X server: %w", what, os.ErrDeadlineExceeded)
79}
80
81// bootstrap negotiates every extension root knows (one pipelined
82// QueryExtension each, then BIG-REQUESTS Enable when present) in one Sync,
83// before deadline.
84func (c *Conn) bootstrap(deadline time.Time) error {
85 for id := ExtCore + 1; id < extCount; id++ {
86 seq := encodeQueryExtension(c.buf, ExtXName[id])
87 if seq == 0 {
88 return c.buf.Err()
89 }
90 if err := c.expect(seq, kindQueryExtension, [4]uint32{uint32(id)}); err != nil {
91 return err
92 }
93 }
94 // consumeReply queues bigreq Enable when BIG-REQUESTS is present; the
95 // wait covers it too.
96 return c.sync(nil, deadline)
97}
98
99// Sync flushes every queued request and blocks until the server has
100// processed them and every request registered with Expect has completed. It
101// appends a GetInputFocus as a fence, so even with nothing registered it is a
102// full round trip: errors of earlier requests have been delivered when it
103// returns. Packets read meanwhile go to h as with Dispatch (nil h: the last
104// Handler given to Dispatch, or none). An error on a request root issued
105// itself is returned as *XError. Sync gives up after 5 s with an error
106// wrapping os.ErrDeadlineExceeded (a hung server); the connection is then
107// still usable but its state unknown, treat it as dead. Meant for startup;
108// called from a Handler callback it returns an error at once.
109func (c *Conn) Sync(h *Handler) error {
110 return c.sync(h, time.Now().Add(c.timeout))
111}
112
113func (c *Conn) sync(h *Handler, deadline time.Time) error {
114 if c.dispatching {
115 return errReentrantDispatch
116 }
117 if h == nil {
118 h = c.h
119 }
120 c.syncFailed = false
121 if err := c.waitUntil(h, deadline); err != nil {
122 return err
123 }
124 if c.syncFailed {
125 e := c.syncErr
126 return &e
127 }
128 return nil
129}
130
131// waitUntil fences, flushes and dispatches, one bounded Dispatch at a time,
132// until the fence has been answered and no registered slot remains, or
133// deadline passes. A fence is re-queued whenever callbacks registered
134// requests after the last one.
135func (c *Conn) waitUntil(h *Handler, deadline time.Time) error {
136 if c.fd < 0 {
137 return ErrClosed
138 }
139 var fence Seq
140 for {
141 if fence != 0 && c.seq >= fence && c.live == 0 {
142 return nil
143 }
144 if fence == 0 || fence < c.buf.next-1 {
145 // Requests queued after the fence (or none yet): fence them.
146 if fence = c.fence(); fence == 0 {
147 return c.buf.Err()
148 }
149 }
150 if err := c.pollOnce(h, deadline); err != nil {
151 return err
152 }
153 }
154}
155
156// pollOnce makes one step of progress: flush, dispatch what is readable and,
157// only when Dispatch read nothing at all, block until the socket is readable
158// (or writable while requests remain queued) or deadline passes. Dispatch is
159// bounded per call, so it may return with input left; the caller loops and
160// re-checks. A deadline already past fails even while packets keep coming
161// (a server flooding events but never answering).
162func (c *Conn) pollOnce(h *Handler, deadline time.Time) error {
163 left := time.Until(deadline)
164 if left <= 0 {
165 return deadlineError("blocking round trip")
166 }
167 events := int16(unix.POLLIN)
168 switch err := c.Flush(); err {
169 case nil:
170 case ErrWouldBlock:
171 events |= unix.POLLOUT
172 default:
173 return err
174 }
175 if err := c.Dispatch(h); err != nil {
176 return err
177 }
178 // Packets handled may have queued requests (callbacks, the bootstrap):
179 // loop to flush them, and to fence them, before blocking.
180 if !c.rdIdle || (events&unix.POLLOUT == 0 && c.buf.Len() > 0) {
181 return nil
182 }
183 return pollFd(c.fd, events, left)
184}
185
186// pollFd blocks until fd has one of events or timeout passes (returning nil;
187// callers re-check their deadline). EINTR is a wake-up.
188func pollFd(fd int, events int16, timeout time.Duration) error {
189 ms := int((timeout + time.Millisecond - 1) / time.Millisecond)
190 fds := []unix.PollFd{{Fd: int32(fd), Events: events}}
191 if _, err := unix.Poll(fds, ms); err != nil && err != unix.EINTR {
192 return os.NewSyscallError("poll", err)
193 }
194 return nil
195}