1package wayland
2
3import (
4 "github.com/bnema/purego-libwayland/protocol/presentationtime"
5 "image"
6 "time"
7
8 "github.com/bnema/neferwl/internal/ports"
9 "github.com/bnema/purego-libwayland/protocol/wayland"
10 "github.com/bnema/purego-libwayland/protocol/wlrlayershell"
11 "github.com/bnema/purego-libwayland/protocol/xdgshell"
12)
13
14type roleKind uint8
15
16const (
17 roleNone roleKind = iota
18 roleXDG
19 roleSubsurface
20 roleLayer
21 roleCursor
22)
23
24type surface struct {
25 wl *wayland.Surface
26 viewport *viewport
27 // bufferScale is committed state; pendingScale is set by set_buffer_scale.
28 bufferScale, pendingScale int
29 kind roleKind
30 xdg *xdgSurface
31 layer *layerSurface
32 server *Server
33 current, pending *wayland.Buffer
34 attached bool
35 lastW, lastH int // last logged buffer size
36 callbacks []*wayland.Callback
37 role func(bool)
38 destroyed bool
39 // hotX, hotY is the click point of a cursor surface (logical).
40 hotX, hotY int
41 // on is the output the surface entered; scale is the last scale sent.
42 on *output
43 scale float64
44 // content is the last content built from this surface's own buffer;
45 // has is false while no buffer is attached.
46 content ports.SurfaceContent
47 has bool
48 sub subState
49 // tearing is the surface's wp_tearing_control_v1; async is the
50 // committed hint, pendingAsync the requested one.
51 tearing *tearingHandler
52 async, pendingAsync bool
53 // Presentation constraints (fifo.go): pending fifo requests and commit
54 // timestamp, the barrier, and the commits waiting to apply.
55 fifo *fifoHandler
56 timer *timerHandler
57 pendingBarrier, pendingWait bool
58 pendingTime time.Time
59 barrier bool
60 barrierAt time.Time
61 queue []update
62 // contentType is the wp_content_type_v1; contentKind the committed type.
63 contentType *contentTypeHandler
64 contentKind, pendingKind uint32
65 // Damage requested since the last commit: in surface (logical) and
66 // buffer pixels; committed is what the last commit changed, in buffer
67 // pixels (full: everything), read by the window's damage history.
68 pendingDamage, pendingBufDamage []ports.Rect
69 committed damage
70 // pendingFeedback are wp_presentation feedbacks for the next commit.
71 pendingFeedback []*presentationtime.WpPresentationFeedback
72 // Explicit sync (syncobj.go): the surface's syncobj object; the
73 // committed points of the pending commit (pendingSync, with the wait
74 // for its acquire point); and the current buffer's hold.
75 sync *syncState
76 pendingSync *commitSync
77 hold syncHold
78}
79
80// commitSync is a commit's explicit-sync points and acquire wait.
81type commitSync struct {
82 acquire, release syncPoint
83 wait *syncWait
84}
85
86// damage is what one commit changed in a surface's buffer.
87type damage struct {
88 full bool
89 rects []ports.Rect
90}
91
92// maxDamageRects bounds one commit's rects: past it the commit is full.
93const maxDamageRects = 32
94
95// subState is the subsurface tree: parent is set on subsurfaces, children
96// are ordered bottom to top, below marks children under their parent.
97type subState struct {
98 // role is the live wl_subsurface, nil when the surface has none.
99 role *wayland.Subsurface
100 parent *surface
101 children []*surface
102 x, y int // committed position, from the parent origin
103 pendX, pendY int
104 moved bool // a position waits for the parent commit
105 below bool
106}
107
108// root is the top of the surface's subsurface tree.
109func (s *surface) root() *surface {
110 for s.sub.parent != nil {
111 s = s.sub.parent
112 }
113 return s
114}
115
116// windowID is the mapped window or layer the surface draws, or 0.
117func (s *surface) windowID() ports.WindowID {
118 if s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped {
119 return s.xdg.window.id
120 }
121 if s.layer != nil && s.layer.mapped {
122 return s.layer.id
123 }
124 return 0
125}
126
127// tree is the root's content with its subsurfaces flattened.
128func (s *surface) tree(id ports.WindowID) ports.SurfaceContent {
129 c := s.content
130 c.ID = id
131 c.Children = nil
132 for _, ch := range s.sub.children {
133 ch.appendTree(&c.Children, ch.sub.x, ch.sub.y, ch.sub.below)
134 }
135 if s.xdg != nil {
136 c.Geometry = s.xdg.geometry
137 }
138 c.Async = s.async
139 return c
140}
141
142// appendTree flattens a subsurface at (x, y) from the root: its children
143// below it, itself, then its children above.
144func (s *surface) appendTree(out *[]ports.Subsurface, x, y int, below bool) {
145 for _, ch := range s.sub.children {
146 if ch.sub.below {
147 ch.appendTree(out, x+ch.sub.x, y+ch.sub.y, below)
148 }
149 }
150 if s.has {
151 *out = append(*out, ports.Subsurface{X: x, Y: y, Below: below, SurfaceContent: s.content})
152 }
153 for _, ch := range s.sub.children {
154 if !ch.sub.below {
155 ch.appendTree(out, x+ch.sub.x, y+ch.sub.y, below)
156 }
157 }
158}
159
160// redraw sends the tree of a mapped root to the outputs. Only a commit
161// of the root's own buffer with no other change keeps partial damage.
162func (s *surface) redraw() {
163 r := s.root()
164 if id := r.windowID(); id != 0 && s.server.channels.Contents != nil {
165 d := damage{full: true}
166 if s == r && len(r.sub.children) == 0 {
167 d = r.committed
168 }
169 s.server.emitContent(r.tree(id), d)
170 }
171}
172
173// detach removes a subsurface from its parent and redraws the parent.
174func (s *surface) detach() {
175 p := s.sub.parent
176 if p == nil {
177 return
178 }
179 for i, ch := range p.sub.children {
180 if ch == s {
181 p.sub.children = append(p.sub.children[:i:i], p.sub.children[i+1:]...)
182 break
183 }
184 }
185 s.sub.parent = nil
186 p.redraw()
187 s.sendScale()
188}
189
190func (s *surface) Destroy(*wayland.Surface) {
191 s.destroyed = true
192 for _, fb := range s.pendingFeedback {
193 discard(fb)
194 }
195 s.pendingFeedback = nil
196 s.tearing = nil // the control becomes inert
197 s.dropQueue()
198 if s.server.cursorSurface == s {
199 // The pointer keeps no cursor until the client sets another.
200 s.server.cursorSurface = nil
201 s.server.setCursor(ports.CursorChange{Hidden: true})
202 }
203 s.detach()
204 for _, ch := range s.sub.children {
205 ch.sub.parent = nil
206 }
207 s.sub.children = nil
208 for _, cb := range s.callbacks {
209 cb.Destroy()
210 }
211 s.callbacks = nil
212 // The client may reuse the buffer on another surface.
213 s.dropSync(s.pendingSync)
214 s.pendingSync = nil
215 if s.current != nil {
216 s.server.releaseBuffer(s, s.current, s.hold)
217 s.hold = syncHold{}
218 }
219 s.current, s.pending = nil, nil
220 if s.role != nil {
221 s.role(false)
222 }
223 if c := s.server.constraints[s]; c != nil {
224 s.server.dropConstraint(c)
225 }
226}
227func (s *surface) Attach(_ *wayland.Surface, b *wayland.Buffer, _, _ int32) {
228 s.pending = b
229 s.attached = true
230}
231func (s *surface) Frame(r *wayland.Surface, id uint32) {
232 cb, err := wayland.NewCallback(r.Client(), 1, id, struct{}{})
233 if err == nil {
234 s.callbacks = append(s.callbacks, cb)
235 }
236}
237func (s *surface) Commit(*wayland.Surface) {
238 if s.layer != nil && s.attached && s.pending != nil && !s.layer.acked {
239 s.layer.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "buffer before configure ack")
240 return
241 }
242 if s.xdg != nil && s.attached && s.pending != nil && !s.xdg.acked {
243 s.xdg.resource.PostError(uint32(xdgshell.SurfaceErrorUnconfiguredBuffer), "buffer before initial configure ack")
244 return
245 }
246 if !s.checkSyncCommit() {
247 return
248 }
249 s.takeSyncPoints()
250 // A commit behind a fifo barrier, a future timestamp or an acquire
251 // point without a fence yet waits (fifo.go).
252 if s.mustWait(time.Now()) {
253 s.queueUpdate()
254 return
255 }
256 s.applyCommit()
257}
258
259// applyCommit makes the pending state current.
260func (s *surface) applyCommit() {
261 fresh := s.attached && s.pending != nil
262 if s.pendingBarrier {
263 s.pendingBarrier = false
264 s.setBarrier(time.Now())
265 }
266 s.pendingWait, s.pendingTime = false, time.Time{}
267 if s.contentKind != s.pendingKind {
268 s.contentKind = s.pendingKind
269 s.server.log.Info().Uint64("id", uint64(s.root().windowID())).Uint32("content_type", s.contentKind).Msg("content type")
270 }
271 if s.pendingScale > 0 {
272 s.bufferScale = s.pendingScale
273 }
274 hinted := s.async != s.pendingAsync
275 s.async = s.pendingAsync
276 if s.viewport != nil {
277 s.viewport.commit()
278 }
279 cs := s.pendingSync
280 s.pendingSync = nil
281 if s.attached {
282 if s.current != nil && (s.pending == nil || s.current.Resource != s.pending.Resource) {
283 s.server.releaseBuffer(s, s.current, s.hold)
284 } else if cs != nil || s.pending == nil {
285 // Same buffer again, or detached: the old points are done
286 // with once the new ones take over.
287 s.server.releaseBufferSync(s, s.hold)
288 }
289 s.hold = syncHold{}
290 s.current = s.pending
291 s.pending = nil
292 s.attached = false
293 }
294 s.applySync(cs)
295 if len(s.callbacks) > 0 {
296 s.server.queueFrames(s.server.frameOutput(s), s.callbacks)
297 }
298 s.callbacks = nil
299 if s.role != nil {
300 s.role(s.current != nil)
301 }
302 if fresh {
303 if state, ok := s.server.buffers[s.current.Resource]; ok {
304 if c, ok := state.content(0); ok {
305 c.LogicalW, c.LogicalH = s.logicalSize(c.Width, c.Height)
306 resized := !s.has || c.Width != s.content.Width || c.Height != s.content.Height || c.LogicalW != s.content.LogicalW || c.LogicalH != s.content.LogicalH
307 s.commitDamage(true, resized, c.Width, c.Height)
308 if s.sub.parent == nil && (c.Width != s.lastW || c.Height != s.lastH) {
309 s.lastW, s.lastH = c.Width, c.Height
310 s.server.log.Info().Uint64("id", uint64(s.windowID())).Int("w", c.Width).Int("h", c.Height).Msg("buffer size")
311 }
312 c.Acquire = s.hold.acquire
313 s.content, s.has = c, true
314 } else if shm, ok := state.(*buffer); ok {
315 shm.pool.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "SHM backing file truncated")
316 }
317 // Buffers are read in place: released when the next one
318 // replaces them.
319 }
320 }
321 if !fresh {
322 s.commitDamage(false, false, 0, 0)
323 }
324 if s.current == nil {
325 s.content, s.has = ports.SurfaceContent{}, false
326 }
327 // Subsurface positions apply on the parent commit.
328 moved := false
329 for _, ch := range s.sub.children {
330 if ch.sub.moved {
331 ch.sub.x, ch.sub.y, ch.sub.moved = ch.sub.pendX, ch.sub.pendY, false
332 moved = true
333 }
334 }
335 geometry := false
336 if s.xdg != nil && s.xdg.pendingGeometry != s.xdg.geometry {
337 s.xdg.geometry, geometry = s.xdg.pendingGeometry, true
338 }
339 s.commitConstraint(geometry)
340 if s.xdg != nil && s.xdg.window != nil {
341 s.xdg.window.afterCommit()
342 }
343 drawn := fresh || moved || geometry || hinted || s.sub.parent != nil
344 if drawn {
345 if moved || geometry {
346 s.committed = damage{full: true}
347 }
348 s.redraw()
349 }
350 fb := s.pendingFeedback
351 s.pendingFeedback = nil
352 s.commitFeedback(fb, fresh)
353}
354func (s *surface) Damage(_ *wayland.Surface, x, y, w, h int32) {
355 if w > 0 && h > 0 && len(s.pendingDamage) <= maxDamageRects {
356 s.pendingDamage = append(s.pendingDamage, ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)})
357 }
358}
359func (s *surface) DamageBuffer(_ *wayland.Surface, x, y, w, h int32) {
360 if w > 0 && h > 0 && len(s.pendingBufDamage) <= maxDamageRects {
361 s.pendingBufDamage = append(s.pendingBufDamage, ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)})
362 }
363}
364
365// commitDamage turns the requested damage into buffer pixels of a bw×bh
366// buffer. Surface damage scales by the buffer/logical ratio, rounded out.
367// A new size, too many rects or none at all with a new buffer (clients
368// that do not report damage) is a full change.
369func (s *surface) commitDamage(fresh, resized bool, bw, bh int) {
370 surf, buf := s.pendingDamage, s.pendingBufDamage
371 s.pendingDamage, s.pendingBufDamage = nil, nil
372 if !fresh {
373 s.committed = damage{}
374 return
375 }
376 if resized || len(surf)+len(buf) == 0 || len(surf)+len(buf) > maxDamageRects {
377 s.committed = damage{full: true}
378 return
379 }
380 lw, lh := s.logicalSize(bw, bh)
381 d := damage{rects: make([]ports.Rect, 0, len(surf)+len(buf))}
382 bounds := image.Rect(0, 0, bw, bh)
383 add := func(r image.Rectangle) {
384 if r = r.Intersect(bounds); !r.Empty() {
385 d.rects = append(d.rects, ports.Rect{X: r.Min.X, Y: r.Min.Y, W: r.Dx(), H: r.Dy()})
386 }
387 }
388 for _, r := range buf {
389 add(image.Rect(r.X, r.Y, r.X+r.W, r.Y+r.H))
390 }
391 for _, r := range surf {
392 if lw <= 0 || lh <= 0 {
393 s.committed = damage{full: true}
394 return
395 }
396 // Clients may damage INT32_MAX-sized rects: clamp before scaling.
397 x0, y0 := max(r.X, 0), max(r.Y, 0)
398 x1, y1 := min(r.X+min(r.W, lw), lw), min(r.Y+min(r.H, lh), lh)
399 add(image.Rect(x0*bw/lw, y0*bh/lh, (x1*bw+lw-1)/lw, (y1*bh+lh-1)/lh))
400 }
401 s.committed = d
402}
403func (*surface) SetOpaqueRegion(*wayland.Surface, *wayland.Region) {}
404func (*surface) SetInputRegion(*wayland.Surface, *wayland.Region) {}
405func (*surface) SetBufferTransform(*wayland.Surface, int32) {}
406func (s *surface) SetBufferScale(r *wayland.Surface, v int32) {
407 if v < 1 {
408 r.PostError(uint32(wayland.SurfaceErrorInvalidScale), "buffer scale must be positive")
409 return
410 }
411 s.pendingScale = int(v)
412}
413func (*surface) Offset(*wayland.Surface, int32, int32) {}
414func (*surface) GetRelease(*wayland.Surface, uint32) {}