1package wayland
2
3import (
4 "image"
5 "math"
6 "os"
7 "slices"
8 "time"
9
10 "github.com/bnema/go-wayland-bindings/server/presentationtime"
11
12 "github.com/bnema/go-wayland-bindings/server/wayland"
13 "github.com/bnema/go-wayland-bindings/server/wlrlayershell"
14 "github.com/bnema/go-wayland-bindings/server/xdgshell"
15 "github.com/bnema/neferwl/internal/ports"
16 "github.com/rs/zerolog"
17)
18
19type roleKind uint8
20
21const (
22 roleNone roleKind = iota
23 roleXDG
24 roleSubsurface
25 roleLayer
26 roleCursor
27 roleInputPopup
28 roleSessionLock
29 // roleDragIcon is the icon of a data drag (dnd.go).
30 roleDragIcon
31)
32
33type surface struct {
34 wl *wayland.Surface
35 viewport *viewport
36 committedViewport viewportState // value snapshot; no allocation on viewport commits
37 // next is the state requested since the last commit (see pendingCommit).
38 next pendingCommit
39 // syncScratch holds next.sync between takeSyncPoints and queueUpdate,
40 // which moves it into the update.
41 syncScratch commitSync
42 // bufferScale, transform and opaque are committed state. formatOpaque is
43 // the current buffer's own opacity (x formats).
44 bufferScale int
45 transform ports.BufferTransform
46 opaque []ports.Rect
47 formatOpaque bool
48 kind roleKind
49 xdg *xdgSurface
50 layer *layerSurface
51 lock *lockSurface
52 // bufferCommitted is permanent, including commits later discarded by FIFO.
53 bufferCommitted bool
54 server *Server
55 current *wayland.Buffer
56 lastW, lastH int // last logged buffer size
57 role func(bool)
58 destroyed bool
59 // hotX, hotY is the click point of a cursor surface (logical).
60 hotX, hotY int
61 // on is the output the surface entered; scale is the last scale sent.
62 on *output
63 scale float64
64 // prefTransform is the last preferred buffer transform sent (wl_surface
65 // v6+); zero (normal) is the protocol default.
66 prefTransform ports.BufferTransform
67 // content is the last content built from this surface's own buffer;
68 // has is false while no buffer is attached.
69 inputAll bool
70 inputRects []ports.Rect
71 sentInput, lastInputAll bool
72 lastInputRects []ports.Rect
73 content ports.SurfaceContent
74 has bool
75 identity uint64
76 version uint64
77 // cachedTree is never modified once returned to emitContent.
78 cachedTree []ports.Subsurface
79 treeDirty bool
80 commitFresh, commitSkipped bool
81 queuedScale int
82 queuedBuffer uint32
83 sub subState
84 // tearing is the surface's wp_tearing_control_v1; async the committed
85 // hint.
86 tearing *tearingHandler
87 async bool
88 colorControl *colorSurface
89 color SurfaceColor
90 representation surfaceRepresentation
91 representationControl *representationSurface
92 // Presentation constraints (fifo.go): the barrier, and the commits
93 // waiting to apply.
94 fifo *fifoHandler
95 timer *timerHandler
96 barrier bool
97 barrierAt time.Time
98 queue []*update
99 // contentType is the wp_content_type_v1; contentKind the committed type.
100 contentType *contentTypeHandler
101 // alpha is the surface's wp_alpha_modifier_surface_v1.
102 alpha *alphaHandler
103 contentKind uint32
104 // committed is what the last commit changed, in buffer pixels (full:
105 // everything), read by the window's damage history.
106 committed damage
107 // drewChildren is whether the root's last published tree drew a
108 // subsurface: one that stops drawing leaves its area to redraw.
109 drewChildren bool
110 // Explicit sync (syncobj.go): the surface's syncobj object and the
111 // current buffer's hold.
112 sync *syncState
113 hold syncHold
114}
115
116// pendingCommit is the double-buffered wl_surface state requested since the
117// last commit. A commit captures it whole into an update (takePending):
118// one-shot parts are cleared, sticky parts (scale, transform, hints, color)
119// carry over to the next commit.
120type pendingCommit struct {
121 // buffer is attached (attached reports an attach request, nil detaches).
122 buffer *wayland.Buffer
123 attached bool
124 // scale is set by set_buffer_scale, transform by set_buffer_transform.
125 scale int
126 transform ports.BufferTransform
127 // opaque and the input region are set by their requests (the Set flags).
128 opaque []ports.Rect
129 opaqueSet bool
130 inputAll, inputSet bool
131 inputRects []ports.Rect
132 callbacks []*wayland.Callback
133 async bool
134 color SurfaceColor
135 representation surfaceRepresentation
136 kind uint32
137 // fade is 1 - the wp_alpha_modifier_v1 multiplier (sticky).
138 fade float32
139 // barrier and wait are fifo requests; at the commit timestamp.
140 barrier, wait bool
141 at time.Time
142 // dx, dy are the last attach offset or wl_surface.offset request since
143 // the commit (the latest replaces the earlier); only a drag icon uses
144 // them.
145 dx, dy int32
146 // damage is in surface (logical) pixels, bufDamage in buffer pixels.
147 damage, bufDamage []ports.Rect
148 // feedback are wp_presentation feedbacks for this commit.
149 feedback []*presentationtime.WpPresentationFeedback
150 // sync is the commit's explicit-sync points, with the wait for its
151 // acquire point.
152 sync *commitSync
153}
154
155// commitSync is a commit's explicit-sync points and acquire wait.
156type commitSync struct {
157 acquire, release syncPoint
158 wait *syncWait
159 fence *os.File // exported before application; owned until apply/drop
160}
161
162// damage is what one commit changed in a surface's buffer.
163type damage struct {
164 full bool
165 rects []ports.Rect
166}
167
168// maxDamageRects bounds one commit's rects: past it the commit is full.
169const maxDamageRects = 32
170
171// subState is the subsurface tree: parent is set on subsurfaces, children
172// are ordered bottom to top, below marks children under their parent.
173type subState struct {
174 // role is the live wl_subsurface, nil when the surface has none.
175 role *wayland.Subsurface
176 parent *surface
177 children []*surface // structural children
178 pendingLayout, layout []childLayout
179 synced bool // explicit synchronization mode
180}
181
182// root is the top of the surface's subsurface tree.
183func (s *surface) root() *surface {
184 for s.sub.parent != nil {
185 s = s.sub.parent
186 }
187 return s
188}
189
190// windowID is the mapped window or layer the surface draws, or 0.
191func (s *surface) windowID() ports.WindowID {
192 if s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped {
193 return s.xdg.window.id
194 }
195 if s.layer != nil && s.layer.mapped {
196 return s.layer.id
197 }
198 if s.lock != nil && s.lock.mapped {
199 return s.lock.id
200 }
201 if s.kind == roleDragIcon && s.server.dnd != nil && s.server.dnd.icon == s {
202 return s.server.dnd.iconID
203 }
204 return 0
205}
206
207// outputColor converts the committed protocol description without allocating.
208func (s *surface) outputColor() ports.SurfaceColor {
209 return ports.SurfaceColor{TF: uint8(s.color.TF), Primaries: uint8(s.color.Primaries), MaxCLL: uint16(s.color.MaxCLL), MaxFALL: uint16(s.color.MaxFALL), Coefficients: s.representation.coefficients, Range: s.representation.rangeValue, Chroma: s.representation.chroma}
210}
211
212func (s *surface) contentWithColor() ports.SurfaceContent {
213 c := s.content
214 c.Surface, c.Version = s.identity, s.version
215 c.Color = s.outputColor()
216 // A subsurface asks for tearing itself: the output may scan it out.
217 c.Async = s.async
218 return c
219}
220
221// tree is the root's content with its subsurfaces flattened.
222func (s *surface) tree(id ports.WindowID) ports.SurfaceContent {
223 c := s.contentWithColor()
224 c.ID = id
225 c.ContentType = ports.ContentType(s.contentKind)
226 if s.treeDirty || s.cachedTree == nil {
227 count := s.treeCount()
228 children := make([]ports.Subsurface, 0, count)
229 for _, item := range s.sub.layout {
230 item.child.appendTree(&children, item.x, item.y, item.below)
231 }
232 s.cachedTree, s.treeDirty = children, false
233 }
234 c.Children = s.cachedTree
235 if s.xdg != nil {
236 c.Geometry = s.xdg.geometry
237 }
238 return c
239}
240
241func (s *surface) treeCount() int {
242 n := 0
243 for _, item := range s.sub.layout {
244 if item.child.has {
245 n++
246 }
247 n += item.child.treeCount()
248 }
249 return n
250}
251
252// appendTree flattens a subsurface at (x, y) from the root: its children
253// below it, itself, then its children above.
254func (s *surface) appendTree(out *[]ports.Subsurface, x, y int, below bool) {
255 for _, item := range s.sub.layout {
256 if item.below {
257 item.child.appendTree(out, x+item.x, y+item.y, below)
258 }
259 }
260 if s.has {
261 *out = append(*out, ports.Subsurface{X: x, Y: y, Below: below, SurfaceContent: s.contentWithColor()})
262 }
263 for _, item := range s.sub.layout {
264 if !item.below {
265 item.child.appendTree(out, x+item.x, y+item.y, below)
266 }
267 }
268}
269
270// redraw sends the tree of a mapped root to the outputs. Only a commit
271// of the root's own buffer, with no subsurface drawn before or now, keeps
272// partial damage: subsurfaces without a buffer (clients often keep one
273// for later) are not drawn, so they do not make it full.
274func (s *surface) redraw() {
275 r := s.root()
276 if id := r.windowID(); id != 0 {
277 c := r.tree(id)
278 d := damage{full: true}
279 if s == r && len(c.Children) == 0 && !r.drewChildren {
280 d = r.committed
281 }
282 r.drewChildren = len(c.Children) > 0
283 s.server.emitContent(c, d)
284 }
285}
286
287// detach removes a subsurface from its parent and redraws the parent.
288func (s *surface) detach() {
289 p := s.sub.parent
290 if p == nil {
291 return
292 }
293 for i, ch := range p.sub.children {
294 if ch == s {
295 p.sub.children = append(p.sub.children[:i:i], p.sub.children[i+1:]...)
296 break
297 }
298 }
299 p.sub.pendingLayout = removeLayout(p.sub.pendingLayout, s)
300 p.sub.layout = removeLayout(p.sub.layout, s)
301 p.root().treeDirty = true
302 for _, u := range p.queue {
303 u.layout = removeLayout(u.layout, s)
304 }
305 s.sub.parent = nil
306 // Parent updates cannot make a detached child's bound commits visible.
307 s.dropQueue()
308 s.flushDesync()
309 p.redraw()
310 p.emitInput()
311 s.sendScale()
312}
313
314func (s *surface) Destroy(*wayland.Surface) {
315 s.destroyed = true
316 for _, fb := range s.next.feedback {
317 discard(fb)
318 }
319 s.next.feedback = nil
320 s.tearing = nil // the control becomes inert
321 s.colorControl = nil
322 s.representationControl = nil
323 s.dropQueue()
324 if s.server.cursorSurface == s {
325 // The pointer keeps no cursor until the client sets another.
326 s.server.cursorSurface = nil
327 s.server.setCursor(ports.CursorChange{Hidden: true})
328 }
329 s.detach()
330 for _, ch := range s.sub.children {
331 ch.sub.parent = nil
332 ch.dropQueue()
333 ch.flushDesync()
334 }
335 s.sub.children = nil
336 s.sub.layout, s.sub.pendingLayout = nil, nil
337 for _, cb := range s.next.callbacks {
338 cb.Destroy()
339 }
340 s.next.callbacks = nil
341 // The client may reuse the buffer on another surface.
342 s.dropSync(s.next.sync)
343 s.next.sync = nil
344 if s.current != nil {
345 s.server.releaseBuffer(s, s.current, s.hold)
346 s.hold = syncHold{}
347 }
348 s.current, s.next.buffer = nil, nil
349 if s.role != nil {
350 s.role(false)
351 }
352 if c := s.server.constraints[s]; c != nil {
353 s.server.dropConstraint(c)
354 }
355 s.server.dndSurfaceGone(s)
356}
357
358// Attach: before wl_surface v5 the offset rides on attach; only a drag
359// icon uses it.
360func (s *surface) Attach(r *wayland.Surface, b *wayland.Buffer, dx, dy int32) {
361 s.next.buffer = b
362 s.next.attached = true
363 if r.Version() < 5 {
364 s.next.dx, s.next.dy = dx, dy
365 }
366}
367func (s *surface) Frame(r *wayland.Surface, id uint32) {
368 cb, err := wayland.NewCallback(r.Client(), 1, id, struct{}{})
369 if err == nil {
370 s.next.callbacks = append(s.next.callbacks, cb)
371 }
372}
373func (s *surface) Commit(*wayland.Surface) {
374 if s.next.attached && s.next.buffer != nil {
375 s.bufferCommitted = true
376 }
377 if s.lock != nil && !s.lock.checkCommit() {
378 return
379 }
380 if s.layer != nil && s.next.attached && s.next.buffer != nil && !s.layer.acked {
381 s.layer.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "buffer before configure ack")
382 return
383 }
384 if s.xdg != nil && s.next.attached && s.next.buffer != nil && !s.xdg.acked {
385 s.xdg.resource.PostError(uint32(xdgshell.SurfaceErrorUnconfiguredBuffer), "buffer before initial configure ack")
386 return
387 }
388 if !s.checkSyncCommit() || !s.checkRepresentationCommit() {
389 return
390 }
391 s.takeSyncPoints()
392 // Each request creates an ordered content update. A synchronized update
393 // can only be reached through a parent's committed dependency graph.
394 s.queueAndTick()
395}
396
397// queueAndTick queues the commit and applies what is ready. The pacer is
398// woken at most once, after the work, and by a tick on this goroutine
399// rather than by a wake that makes the pacer call Do back.
400func (s *surface) queueAndTick() {
401 srv := s.server
402 srv.paceDepth++
403 s.queueUpdate()
404 srv.tickFifo(time.Now(), nil)
405 if n := len(s.queue); n > 0 && !s.queue[n-1].synced {
406 // The commit waits for a barrier, a fence or a refresh: the pacer
407 // must time it. A commit applied at once needs no tick of its own;
408 // its callbacks, barrier and held buffers wake the pacer where
409 // they are queued. A synchronized commit waits for its parent's
410 // commit, which ticks for it.
411 srv.wakePacer()
412 }
413 srv.paceDepth--
414 if srv.paceDepth == 0 && srv.paceDirty {
415 srv.paceDirty = false
416 srv.paceNow()
417 }
418}
419
420// applyCommit makes only the captured update current. Live pending requests
421// remain untouched, even when this update waited behind another commit.
422func (s *surface) applyCommit(u *update) {
423 oldW, oldH, oldSource, oldTransform := s.content.LogicalW, s.content.LogicalH, s.content.Source, s.content.Transform
424 oldColor, oldRepresentation := s.color, s.representation
425 fresh := u.attached && u.buffer != nil
426 s.commitFresh = fresh
427 if !s.commitSkipped {
428 s.queuedScale = u.scale
429 s.queuedBuffer = 0
430 if u.buffer != nil {
431 s.queuedBuffer = u.buffer.ID()
432 }
433 }
434 if u.barrier {
435 s.setBarrier(time.Now())
436 }
437 if s.contentKind != u.kind {
438 s.contentKind = u.kind
439 s.server.log.Info().Uint64("id", uint64(s.root().windowID())).Uint32("content_type", s.contentKind).Msg("content type")
440 }
441 if u.scale > 0 {
442 s.bufferScale = u.scale
443 }
444 s.transform = u.transform
445 oldOpaque, oldOpaqueRect := s.content.Opaque, s.content.OpaqueRect
446 if u.opaqueSet {
447 s.opaque = u.opaque
448 }
449 oldFade := s.content.Fade
450 hinted := s.async != u.async || s.color != u.color || s.representation != u.representation
451 if s.async != u.async {
452 // Contents carry the hint of every surface in the tree.
453 s.root().treeDirty = true
454 }
455 s.async = u.async
456 s.color = u.color
457 s.representation = u.representation
458 if s.lock != nil && s.commitSkipped {
459 // A discarded replacement cannot change retained lock geometry, even
460 // if its viewport was destroyed before capture or before application.
461 } else if u.vp != nil && (s.lock != nil || u.vp.resource != nil && u.vp.resource.Resource.Alive()) {
462 s.committedViewport.destW, s.committedViewport.destH, s.committedViewport.dest = u.vpW, u.vpH, u.vpSet
463 s.committedViewport.src, s.committedViewport.crop = u.vpSrc, u.vpCrop
464 } else {
465 s.committedViewport.dest, s.committedViewport.crop = false, false
466 }
467 cs := u.sync
468 if u.attached {
469 if s.current != nil && (u.buffer == nil || s.current.Resource != u.buffer.Resource) {
470 s.server.releaseBuffer(s, s.current, s.hold)
471 } else if cs != nil || u.buffer == nil {
472 // Same buffer again, or detached: the old points are done
473 // with once the new ones take over.
474 s.server.releaseBufferSync(s, s.hold)
475 }
476 s.hold = syncHold{}
477 s.current = u.buffer
478 }
479 s.applySync(cs)
480 if u.inputSet {
481 s.inputAll, s.inputRects = u.inputAll, u.inputRects
482 }
483 if len(u.callbacks) > 0 {
484 s.server.queueFrames(s, u.callbacks)
485 }
486 if s.lock != nil && !s.commitSkipped && !s.lock.checkCaptured(u) {
487 return
488 }
489 if s.role != nil {
490 if u.layer != nil && s.layer == u.layer {
491 u.layer.commitState(u.layerNext, s.current != nil)
492 } else {
493 s.role(s.current != nil)
494 }
495 }
496 if fresh {
497 if state, ok := s.server.buffers[s.current.Resource]; ok {
498 if c, ok := state.content(0); ok {
499 if !s.validateViewport(c.Width, c.Height) {
500 return
501 }
502 c.Transform = s.transform
503 c.Source, _ = s.source(c.Width, c.Height)
504 c.LogicalW, c.LogicalH = s.logicalSize(c.Width, c.Height)
505 s.formatOpaque = c.Opaque
506 c.Opaque = (c.Opaque || s.opaqueCovers(c.LogicalW, c.LogicalH)) && u.fade == 0
507 c.OpaqueRect = s.opaqueRect(c.Opaque, c.LogicalW, c.LogicalH, u.fade)
508 c.Fade = u.fade
509 resized := !s.has || c.Width != s.content.Width || c.Height != s.content.Height || c.LogicalW != s.content.LogicalW || c.LogicalH != s.content.LogicalH
510 s.commitDamage(u, true, resized, c.Width, c.Height)
511 if s.sub.parent == nil && (c.Width != s.lastW || c.Height != s.lastH) {
512 s.lastW, s.lastH = c.Width, c.Height
513 s.server.log.Info().Uint64("id", uint64(s.windowID())).Int("w", c.Width).Int("h", c.Height).Msg("buffer size")
514 }
515 c.Acquire = s.hold.acquire
516 s.content, s.has = c, true
517 } else if shm, ok := state.(*buffer); ok {
518 shm.pool.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "SHM backing file truncated")
519 }
520 // Buffers are read in place: released when the next one
521 // replaces them.
522 }
523 }
524 damaged := !fresh && s.current != nil && (len(u.damage) > 0 || len(u.bufDamage) > 0)
525 if !fresh {
526 s.commitDamage(u, false, false, 0, 0)
527 // A NULL attach is exempt from out_of_buffer: its content is
528 // cleared below, so the retained buffer is not validated.
529 if s.has && s.current != nil {
530 if !s.validateViewport(s.content.Width, s.content.Height) {
531 return
532 }
533 s.content.Transform = s.transform
534 s.content.Source, _ = s.source(s.content.Width, s.content.Height)
535 s.content.LogicalW, s.content.LogicalH = s.logicalSize(s.content.Width, s.content.Height)
536 s.content.Opaque = (s.formatOpaque || s.opaqueCovers(s.content.LogicalW, s.content.LogicalH)) && u.fade == 0
537 s.content.OpaqueRect = s.opaqueRect(s.content.Opaque, s.content.LogicalW, s.content.LogicalH, u.fade)
538 s.content.Fade = u.fade
539 }
540 }
541 if s.current == nil {
542 s.content, s.has = ports.SurfaceContent{}, false
543 }
544 if fresh || damaged || oldW != s.content.LogicalW || oldH != s.content.LogicalH || oldSource != s.content.Source || oldColor != s.color || oldRepresentation != s.representation || oldTransform != s.content.Transform || oldOpaque != s.content.Opaque || oldOpaqueRect != s.content.OpaqueRect || oldFade != s.content.Fade {
545 s.version++
546 s.root().treeDirty = true
547 }
548 // The layout belongs to this update, not to the latest requests.
549 moved := !sameLayout(s.sub.layout, u.layout)
550 if moved {
551 s.root().treeDirty = true
552 }
553 // Queued updates can still reference the previous layout. Never mutate it.
554 if moved {
555 s.sub.layout = u.layout
556 }
557 geometry := false
558 if s.xdg != nil && s.xdg == u.xdg && u.geometry != s.xdg.geometry {
559 s.xdg.geometry, geometry = u.geometry, true
560 }
561 s.commitConstraint(u.cons, u.region, geometry)
562 s.emitInput()
563 if s.kind == roleDragIcon {
564 s.server.dndIconCommit(s, u.dx, u.dy)
565 }
566 if s.xdg != nil && s.xdg.window != nil {
567 s.xdg.window.afterCommit()
568 }
569 if s.lock != nil {
570 // Role callbacks precede content construction; publish lock placement
571 // only once validated current dimensions are available.
572 s.server.lockSurfaceChanged()
573 }
574 reshaped := s.has && (s.content.LogicalW != oldW || s.content.LogicalH != oldH || s.content.Source != oldSource || s.content.Transform != oldTransform)
575 // Opacity changes how every pixel blends, so it repaints the whole surface.
576 opacity := s.has && (s.content.Opaque != oldOpaque || s.content.OpaqueRect != oldOpaqueRect || s.content.Fade != oldFade)
577 drawn := fresh || damaged || moved || geometry || hinted || reshaped || opacity || s.sub.parent != nil
578 if drawn {
579 if moved || geometry || reshaped || opacity {
580 s.committed = damage{full: true}
581 }
582 if s.server.applyingGraph {
583 s.server.graphDrawn = true
584 } else {
585 s.redraw()
586 }
587 }
588 fb := u.feedback
589 if s.server.applyingGraph {
590 s.server.graphFeedback = append(s.server.graphFeedback, graphFeedback{s, fb, fresh})
591 } else {
592 s.commitFeedback(fb, fresh)
593 }
594}
595func (s *surface) Damage(_ *wayland.Surface, x, y, w, h int32) {
596 if w > 0 && h > 0 && len(s.next.damage) <= maxDamageRects {
597 s.next.damage = append(s.next.damage, ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)})
598 }
599}
600func (s *surface) DamageBuffer(_ *wayland.Surface, x, y, w, h int32) {
601 if w > 0 && h > 0 && len(s.next.bufDamage) <= maxDamageRects {
602 s.next.bufDamage = append(s.next.bufDamage, ports.Rect{X: int(x), Y: int(y), W: int(w), H: int(h)})
603 }
604}
605
606// commitDamage turns the requested damage into buffer pixels of a bw×bh
607// buffer. Surface damage scales by the buffer/logical ratio, rounded out.
608// A new size, too many rects or none at all with a new buffer (clients
609// that do not report damage) is a full change.
610func (s *surface) commitDamage(u *update, fresh, resized bool, bw, bh int) {
611 s.mapDamage(u, fresh, resized, bw, bh)
612 if fresh && s.server.logDamage {
613 s.traceDamage(bw, bh)
614 }
615}
616
617// traceDamage logs the damage commitDamage just computed
618// (--debug=wayland-damage): full_buffer is set when the commit is a full
619// change or one rect covers the whole buffer. Rects are already clipped to
620// the buffer, so covering means equal to it.
621func (s *surface) traceDamage(bw, bh int) {
622 d := s.committed
623 full := d.full
624 for _, r := range d.rects {
625 if r.X <= 0 && r.Y <= 0 && r.X+r.W >= bw && r.Y+r.H >= bh {
626 full = true
627 }
628 }
629 arr := zerolog.Arr()
630 for _, r := range d.rects[:min(len(d.rects), maxLoggedDamageRects)] {
631 arr.Dict(zerolog.Dict().Int("x", r.X).Int("y", r.Y).Int("w", r.W).Int("h", r.H))
632 }
633 s.server.log.Debug().Uint64("window", uint64(s.root().windowID())).Int("bw", bw).Int("bh", bh).
634 Int("rects", len(d.rects)).Bool("full_buffer", full).Array("first_rects", arr).Msg("client damage")
635}
636
637// maxLoggedDamageRects bounds the rects one "client damage" entry lists.
638const maxLoggedDamageRects = 4
639
640// mapDamage sets s.committed from the commit's requested damage.
641func (s *surface) mapDamage(u *update, fresh, resized bool, bw, bh int) {
642 surf, buf := u.damage, u.bufDamage
643 if !fresh {
644 s.committed = damage{}
645 return
646 }
647 if resized || len(surf)+len(buf) == 0 || len(surf)+len(buf) > maxDamageRects {
648 s.committed = damage{full: true}
649 return
650 }
651 lw, lh := s.logicalSize(bw, bh)
652 d := damage{rects: make([]ports.Rect, 0, len(surf)+len(buf))}
653 bounds := image.Rect(0, 0, bw, bh)
654 add := func(r image.Rectangle) {
655 if r = r.Intersect(bounds); !r.Empty() {
656 d.rects = append(d.rects, ports.Rect{X: r.Min.X, Y: r.Min.Y, W: r.Dx(), H: r.Dy()})
657 }
658 }
659 for _, r := range buf {
660 add(image.Rect(r.X, r.Y, r.X+r.W, r.Y+r.H))
661 }
662 // Surface damage maps through the crop, in the transformed buffer's
663 // axes, then back through the buffer transform.
664 tw, th := s.transformedSize(bw, bh)
665 sx, sy, sw, sh := 0.0, 0.0, float64(tw), float64(th)
666 if s.committedViewport.crop {
667 v := s.committedViewport.src
668 scale := float64(max(s.bufferScale, 1)) / 256
669 sx, sy, sw, sh = float64(v[0])*scale, float64(v[1])*scale, float64(v[2])*scale, float64(v[3])*scale
670 }
671 for _, r := range surf {
672 if lw <= 0 || lh <= 0 {
673 s.committed = damage{full: true}
674 return
675 }
676 // Clients may damage INT32_MAX-sized rects: clamp before scaling.
677 x0, y0 := max(r.X, 0), max(r.Y, 0)
678 x1, y1 := min(r.X+min(r.W, lw), lw), min(r.Y+min(r.H, lh), lh)
679 ax, ay := s.transform.ToBuffer(sx+float64(x0)*sw/float64(lw), sy+float64(y0)*sh/float64(lh), float64(tw), float64(th))
680 bx, by := s.transform.ToBuffer(sx+float64(x1)*sw/float64(lw), sy+float64(y1)*sh/float64(lh), float64(tw), float64(th))
681 add(image.Rect(int(math.Floor(min(ax, bx))), int(math.Floor(min(ay, by))), int(math.Ceil(max(ax, bx))), int(math.Ceil(max(ay, by)))))
682 }
683 s.committed = d
684}
685
686// SetOpaqueRegion copies the region: a nil region means nothing is opaque.
687func (s *surface) SetOpaqueRegion(_ *wayland.Surface, reg *wayland.Region) {
688 s.next.opaqueSet = true
689 s.next.opaque = nil
690 if reg != nil && reg.Resource != nil {
691 if g := s.server.regions[reg.Resource]; g != nil {
692 s.next.opaque = slices.Clone(g.rects)
693 }
694 }
695}
696
697func (s *surface) SetBufferTransform(r *wayland.Surface, v int32) {
698 if v < 0 || v > 7 {
699 r.PostError(uint32(wayland.SurfaceErrorInvalidTransform), "invalid buffer transform")
700 return
701 }
702 s.next.transform = ports.BufferTransform(v)
703}
704
705// opaqueCovers reports whether the committed opaque region covers the whole
706// w×h surface. A partial region is ignored: it is only an optimization hint.
707func (s *surface) opaqueCovers(w, h int) bool {
708 return w > 0 && h > 0 && covers(s.opaque, ports.Rect{W: w, H: h})
709}
710
711// opaqueRect is the largest rect of the committed opaque region inside the
712// w×h surface, for a surface that is not wholly opaque (opaque) and not
713// faded: clients often leave an edge out of a region that covers nearly
714// all of the surface (a 983×1230 region on a 984×1231 surface). The zero
715// Rect means none.
716func (s *surface) opaqueRect(opaque bool, w, h int, fade float32) ports.Rect {
717 var best ports.Rect
718 if opaque || fade != 0 || w <= 0 || h <= 0 {
719 return best
720 }
721 for _, r := range s.opaque {
722 x0, y0, x1, y1 := max(r.X, 0), max(r.Y, 0), min(r.X+r.W, w), min(r.Y+r.H, h)
723 if x1 > x0 && y1 > y0 && (x1-x0)*(y1-y0) > best.W*best.H {
724 best = ports.Rect{X: x0, Y: y0, W: x1 - x0, H: y1 - y0}
725 }
726 }
727 return best
728}
729
730// maxCoverRects bounds the region covers checks: beyond it, not covered.
731const maxCoverRects = 16
732
733// covers reports whether the union of rects contains all of r. It tests
734// one point per cell of the grid the rect edges draw.
735func covers(rects []ports.Rect, r ports.Rect) bool {
736 if len(rects) == 0 || len(rects) > maxCoverRects {
737 return false
738 }
739 var xsBuf, ysBuf [2*maxCoverRects + 2]int
740 xs, ys := append(xsBuf[:0], r.X, r.X+r.W), append(ysBuf[:0], r.Y, r.Y+r.H)
741 for _, c := range rects {
742 if c.X > r.X && c.X < r.X+r.W {
743 xs = append(xs, c.X)
744 }
745 if e := c.X + c.W; e > r.X && e < r.X+r.W {
746 xs = append(xs, e)
747 }
748 if c.Y > r.Y && c.Y < r.Y+r.H {
749 ys = append(ys, c.Y)
750 }
751 if e := c.Y + c.H; e > r.Y && e < r.Y+r.H {
752 ys = append(ys, e)
753 }
754 }
755 slices.Sort(xs)
756 slices.Sort(ys)
757 for i := 1; i < len(xs); i++ {
758 for j := 1; j < len(ys); j++ {
759 if xs[i] == xs[i-1] || ys[j] == ys[j-1] {
760 continue
761 }
762 x, y := xs[i-1], ys[j-1]
763 if !slices.ContainsFunc(rects, func(c ports.Rect) bool {
764 return x >= c.X && x < c.X+c.W && y >= c.Y && y < c.Y+c.H
765 }) {
766 return false
767 }
768 }
769 }
770 return true
771}
772func (s *surface) SetBufferScale(r *wayland.Surface, v int32) {
773 if v < 1 {
774 r.PostError(uint32(wayland.SurfaceErrorInvalidScale), "buffer scale must be positive")
775 return
776 }
777 s.next.scale = int(v)
778}
779
780// Offset moves the next commit's surface (v5); only a drag icon uses it.
781func (s *surface) Offset(_ *wayland.Surface, dx, dy int32) {
782 s.next.dx, s.next.dy = dx, dy
783}
784func (*surface) GetRelease(*wayland.Surface, uint32) {}