1package neferclient
2
3import (
4 "encoding/binary"
5 "errors"
6 "fmt"
7 "math"
8 "slices"
9 "strings"
10
11 "github.com/bnema/go-wayland-bindings/client/extsessionlock"
12 "github.com/bnema/go-wayland-bindings/client/fractionalscale"
13 "github.com/bnema/go-wayland-bindings/client/linuxdmabuf"
14 "github.com/bnema/go-wayland-bindings/client/linuxdrmsyncobj"
15 "github.com/bnema/go-wayland-bindings/client/viewporter"
16 "github.com/bnema/go-wayland-bindings/client/wayland"
17 "github.com/bnema/go-wayland-bindings/client/wlrlayershell"
18 "github.com/bnema/go-wayland-bindings/client/xdgshell"
19 "github.com/bnema/wlturbo/wl"
20 "golang.org/x/sys/unix"
21)
22
23// LayerLevel is the wlr-layer-shell stacking layer. The zero value is invalid.
24type LayerLevel uint8
25
26const (
27 LayerBackground LayerLevel = iota + 1
28 LayerBottom
29 LayerTop
30 LayerOverlay
31)
32
33// Anchor is a bit set of output edges a layer surface attaches to.
34type Anchor uint8
35
36const (
37 AnchorTop Anchor = 1 << iota
38 AnchorBottom
39 AnchorLeft
40 AnchorRight
41)
42
43const anchorAll = AnchorTop | AnchorBottom | AnchorLeft | AnchorRight
44
45// KeyboardMode is the layer surface keyboard interactivity. The zero value is
46// KeyboardNone. KeyboardOnDemand needs zwlr_layer_shell_v1 version 4.
47type KeyboardMode uint8
48
49const (
50 KeyboardNone KeyboardMode = iota
51 KeyboardExclusive
52 KeyboardOnDemand
53)
54
55// maxLayerDimension bounds a compositor-assigned layer surface extent.
56const maxLayerDimension = 16384
57
58// DefaultLayerNamespace is used when [LayerConfig.Namespace] is empty.
59const DefaultLayerNamespace = "neferclient"
60
61// LayerConfig requests a wlr-layer-shell surface. Width and Height give the
62// initial logical size; an axis anchored to both opposite edges is sized by
63// the compositor and reported through Handler.Configure.
64type LayerConfig struct {
65 Output string // wl_output name; empty lets the compositor choose
66 Namespace string // defaults to [DefaultLayerNamespace]
67 Level LayerLevel
68 Anchors Anchor
69 Keyboard KeyboardMode
70 ExclusiveZone int32 // >0 reserves, 0 neutral, -1 ignores other zones
71 Margin [4]int32 // top, right, bottom, left
72 // InputRects, when non-nil, restricts pointer input to these rectangles
73 // (logical pixels). An empty non-nil slice makes the surface click-through.
74 InputRects []Rect
75 // Width and Height are the requested logical size. They are required on
76 // every axis that is not anchored to both opposite edges.
77 Width, Height int32
78}
79
80type surfaceRole uint8
81
82const (
83 roleToplevel surfaceRole = iota + 1
84 roleLayer
85 roleLock
86)
87
88// Surface is one wl_surface with its role and the DMA-BUF presentation
89// objects. Every method belongs to the owner goroutine.
90type Surface struct {
91 c *Conn
92 id SurfaceID
93 role surfaceRole
94
95 surf *wayland.Surface
96 viewport *viewporter.WpViewport
97 frac *fractionalscale.WpFractionalScale
98 sync *linuxdrmsyncobj.WpLinuxDrmSyncobjSurface
99 feedback *linuxdmabuf.LinuxDmabufFeedback
100 xdg *xdgshell.XdgSurface
101 toplevel *xdgshell.XdgToplevel
102 layer *wlrlayershell.LayerSurface
103 lockSurf *extsessionlock.ExtSessionLockSurface
104 lock *Lock
105
106 width, height int32
107 scale float64
108 pendW, pendH int32 // toplevel size announced before the xdg configure
109 configured bool
110 frameReady bool
111 closed bool
112
113 // destination/opaque state last sent; they are double-buffered surface
114 // state that persists across commits, so unchanged values are not resent.
115 sentDestW, sentDestH int32
116 sentBufScale int32 // starts at 1, the protocol default
117 presented bool // a Present committed: the surface is mapped
118 output uint32 // lock surfaces: the covered wl_output global
119 opaqueSet bool
120 opaqueW, opaqueH int32
121
122 cb *frameCallback
123 req surfaceRequests
124
125 inputRects []Rect
126 inputCustom bool
127
128 buffers map[uint64]importedBuffer
129 timelines map[uint64]*linuxdrmsyncobj.WpLinuxDrmSyncobjTimeline
130
131 fb Feedback
132 table []Format // current format table
133 pending Feedback // round in progress; Formats reused across rounds
134 fbDone bool
135}
136
137// frameCallback is a reusable wl_callback: the library keeps a single frame
138// callback pending per surface, so one proxy object serves all of them.
139type frameCallback struct {
140 wl.BaseProxy
141 q *queue
142 id SurfaceID
143}
144
145func (f *frameCallback) EventSignature(op uint16) (string, bool) {
146 if op == 0 {
147 return "uint,", true
148 }
149 return "", false
150}
151
152func (f *frameCallback) Dispatch(ev *wl.Event) {
153 if ev.Opcode != 0 {
154 return
155 }
156 _ = ev.Uint32()
157 f.Context().Unregister(f)
158 e := event{kind: evFrame, id: uint32(f.id)}
159 f.q.post(&e)
160}
161
162// ID returns the surface's identifier used in Handler callbacks.
163func (s *Surface) ID() SurfaceID { return s.id }
164
165// WLSurface returns the wl_surface proxy, for requests of protocol extensions
166// bound with [Conn.Bind] that take a surface argument, or nil after
167// [Surface.Close]. The Surface owns it: use it only as a request argument; do
168// not call its methods or add handlers. Owner goroutine only.
169//
170// Handlers of objects bound with [Conn.Bind] run on the reader goroutine. To
171// wait for their answer, keep calling [Conn.Dispatch] on [Conn.Wake]: the
172// reader stops reading while the event queue is full.
173func (s *Surface) WLSurface() *wayland.Surface {
174 if s.closed {
175 return nil
176 }
177 return s.surf
178}
179
180// Size returns the current logical size and scale (1 until announced). Before
181// the first configure the size is the requested one (zero for lock surfaces).
182func (s *Surface) Size() (w, h int32, scale float64) { return s.width, s.height, s.scale }
183
184// Feedback returns the latest complete dmabuf feedback, or nil before the
185// first Handler.FeedbackDone. The value and its slice stay valid until the
186// next FeedbackDone; use [Feedback.Clone] to keep it, and [Feedback.Equal] to
187// tell a changed preference from a repeated one.
188func (s *Surface) Feedback() *Feedback {
189 if !s.fbDone {
190 return nil
191 }
192 return &s.fb
193}
194
195// PhysicalSize returns the buffer size in pixels for the current logical size
196// and scale.
197func (s *Surface) PhysicalSize() (int32, int32, error) {
198 if s.scale <= 0 || s.width <= 0 || s.height <= 0 {
199 return 0, 0, errors.New("neferclient: invalid surface geometry")
200 }
201 if s.scale != math.Trunc(s.scale) && s.viewport == nil {
202 return 0, 0, &CapabilityError{Name: viewporter.WpViewporterInterface, Cause: fmt.Errorf("fractional scale %g requires viewporter", s.scale)}
203 }
204 // Ignore sub-nanopixel floating noise at exact integer boundaries.
205 x, y := math.Ceil(float64(s.width)*s.scale-1e-9), math.Ceil(float64(s.height)*s.scale-1e-9)
206 if x > math.MaxInt32 || y > math.MaxInt32 {
207 return 0, 0, errors.New("neferclient: buffer dimensions overflow")
208 }
209 return int32(x), int32(y), nil
210}
211
212func validateLayer(o *LayerConfig) error {
213 switch {
214 case o.Level < LayerBackground || o.Level > LayerOverlay:
215 return fmt.Errorf("neferclient: invalid layer level %d", o.Level)
216 case o.Anchors&^anchorAll != 0:
217 return fmt.Errorf("neferclient: invalid layer anchors %#x", uint8(o.Anchors))
218 case o.Keyboard > KeyboardOnDemand:
219 return fmt.Errorf("neferclient: invalid keyboard mode %d", o.Keyboard)
220 case o.ExclusiveZone < -1:
221 return fmt.Errorf("neferclient: invalid exclusive zone %d", o.ExclusiveZone)
222 case strings.ContainsRune(o.Namespace, 0), strings.ContainsRune(o.Output, 0):
223 return errors.New("neferclient: layer namespace or output contains NUL")
224 case o.Width < 0 || o.Height < 0:
225 return errors.New("neferclient: negative layer size")
226 }
227 if o.Anchors&(AnchorLeft|AnchorRight) != AnchorLeft|AnchorRight && o.Width == 0 ||
228 o.Anchors&(AnchorTop|AnchorBottom) != AnchorTop|AnchorBottom && o.Height == 0 {
229 return errors.New("neferclient: layer size required on axes not anchored to both edges")
230 }
231 return validateRects(o.InputRects)
232}
233
234func validateRects(rects []Rect) error {
235 for i, r := range rects {
236 if r.Width <= 0 || r.Height <= 0 {
237 return fmt.Errorf("neferclient: input rect %d has non-positive size %dx%d", i, r.Width, r.Height)
238 }
239 if int64(r.X)+int64(r.Width) > math.MaxInt32 || int64(r.Y)+int64(r.Height) > math.MaxInt32 {
240 return fmt.Errorf("neferclient: input rect %d overflows", i)
241 }
242 }
243 return nil
244}
245
246// newSurface creates the wl_surface and the add-on objects shared by every
247// role. The reader must be paused: handlers are installed after the requests.
248func (c *Conn) newSurface(role surfaceRole, w, h int32) (*Surface, error) {
249 if c.closed {
250 return nil, ErrClosed
251 }
252 if err := c.ensureCore(); err != nil {
253 return nil, err
254 }
255 c.ensureSeat() // optional: input flows to the Handler once the seat is bound
256 g := &c.g
257 c.nextSurf++
258 s := &Surface{c: c, id: c.nextSurf, role: role, width: w, height: h, scale: 1, sentBufScale: 1,
259 buffers: map[uint64]importedBuffer{}, timelines: map[uint64]*linuxdrmsyncobj.WpLinuxDrmSyncobjTimeline{}}
260 ok := false
261 defer func() {
262 if !ok {
263 s.destroy()
264 }
265 }()
266 var err error
267 if s.surf, err = g.compositor.CreateSurface(); err != nil {
268 return nil, err
269 }
270 q, sid := c.q, uint32(s.id)
271 s.surf.OnPreferredBufferScale(func(f int32) {
272 e := event{kind: evBufferScale, id: sid, a: f}
273 q.post(&e)
274 })
275 if g.viewporter != nil {
276 if s.viewport, err = g.viewporter.GetViewport(s.surf); err != nil {
277 return nil, err
278 }
279 }
280 if g.fscale != nil {
281 if s.frac, err = g.fscale.GetFractionalScale(s.surf); err != nil {
282 return nil, err
283 }
284 s.frac.OnPreferredScale(func(n uint32) {
285 e := event{kind: evPreferredScale, id: sid, a: int32(n)}
286 q.post(&e)
287 })
288 }
289 // The role objects come next (callers); sync surface and feedback after.
290 s.cb = &frameCallback{q: q, id: s.id}
291 s.cb.SetContext(c.wlctx)
292 s.req = wireRequests{s: s}
293 c.surfaces[s.id] = s
294 ok = true
295 return s, nil
296}
297
298// finishSurface creates the explicit-sync and feedback objects once the role
299// exists.
300func (s *Surface) finishSurface() error {
301 c := s.c
302 var err error
303 if s.sync, err = c.g.syncobj.GetSurface(s.surf); err != nil {
304 return err
305 }
306 if s.feedback, err = c.g.dmabuf.GetDefaultFeedback(); err != nil {
307 return err
308 }
309 s.watchFeedback()
310 return nil
311}
312
313func (s *Surface) watchFeedback() {
314 q, sid := s.c.q, uint32(s.id)
315 s.feedback.OnFormatTable(func(fd *wl.OwnedFD, size uint32) {
316 n, err := fd.Take()
317 if err != nil {
318 e := event{kind: evBadEvent, id: sid, a: badFeedbackFD}
319 q.post(&e)
320 return
321 }
322 e := event{kind: evFeedbackTable, id: sid, fd: int32(n), flags: size}
323 q.post(&e)
324 })
325 s.feedback.OnMainDevice(func(dev []byte) {
326 if len(dev) != 8 {
327 e := event{kind: evBadEvent, id: sid, a: badMainDevice}
328 q.post(&e)
329 return
330 }
331 e := event{kind: evFeedbackMainDevice, id: sid, dev: binary.NativeEndian.Uint64(dev)}
332 q.post(&e)
333 })
334 s.feedback.OnTrancheFormats(func(data []byte) {
335 if len(data)%2 != 0 {
336 e := event{kind: evBadEvent, id: sid, a: badTranche}
337 q.post(&e)
338 return
339 }
340 for len(data) > 0 { // chunks of up to maxNameLen/2 indices
341 n := min(len(data), maxNameLen)
342 e := event{kind: evFeedbackFormats, id: sid}
343 e.setBytes(data[:n])
344 q.post(&e)
345 data = data[n:]
346 }
347 })
348 s.feedback.OnDone(func() {
349 e := event{kind: evFeedbackDone, id: sid}
350 q.post(&e)
351 })
352}
353
354// Codes of evBadEvent.
355const (
356 badFeedbackFD int32 = iota + 1
357 badMainDevice
358 badTranche
359 badConfigure
360)
361
362var badEventErrors = [...]string{
363 badFeedbackFD: "neferclient: dmabuf format table descriptor unavailable",
364 badMainDevice: "neferclient: invalid dmabuf main_device length",
365 badTranche: "neferclient: odd dmabuf tranche format list",
366 badConfigure: "neferclient: configure size out of range",
367}
368
369// configureEvent builds a layer or lock configure event, or a badConfigure
370// event when the compositor announced an out-of-range size.
371func configureEvent(kind eventKind, sid, serial, w, h uint32) event {
372 if w > maxLayerDimension || h > maxLayerDimension {
373 return event{kind: evBadEvent, id: sid, a: badConfigure}
374 }
375 return event{kind: kind, id: sid, serial: serial, a: int32(w), b: int32(h)}
376}
377
378// NewLayerSurface creates a wlr-layer-shell surface. It becomes presentable
379// after the first Handler.Configure.
380func (c *Conn) NewLayerSurface(cfg LayerConfig) (*Surface, error) {
381 if c.closed {
382 return nil, ErrClosed
383 }
384 if err := validateLayer(&cfg); err != nil {
385 return nil, err
386 }
387 if err := c.ensureLayerShell(); err != nil {
388 return nil, err
389 }
390 if cfg.Keyboard == KeyboardOnDemand && c.g.layerVersion < 4 {
391 return nil, &CapabilityError{Name: wlrlayershell.LayerShellInterface, Cause: fmt.Errorf("on-demand keyboard requires version 4, negotiated %d", c.g.layerVersion)}
392 }
393 var out *wayland.Output
394 if cfg.Output != "" {
395 for _, o := range c.outputs {
396 if o.done && o.pub.Name == cfg.Output {
397 out = o.proxy
398 break
399 }
400 }
401 if out == nil {
402 return nil, &CapabilityError{Name: wayland.OutputInterface, Cause: fmt.Errorf("output %q not found", cfg.Output)}
403 }
404 }
405 c.pause()
406 defer c.resume()
407 s, err := c.newSurface(roleLayer, cfg.Width, cfg.Height)
408 if err != nil {
409 return nil, err
410 }
411 ok := false
412 defer func() {
413 if !ok {
414 s.destroy()
415 }
416 }()
417 ns := cfg.Namespace
418 if ns == "" {
419 ns = DefaultLayerNamespace
420 }
421 level := [...]uint32{LayerBackground: wlrlayershell.LAYER_BACKGROUND, LayerBottom: wlrlayershell.LAYER_BOTTOM,
422 LayerTop: wlrlayershell.LAYER_TOP, LayerOverlay: wlrlayershell.LAYER_OVERLAY}[cfg.Level]
423 if s.layer, err = c.g.layer.GetLayerSurface(s.surf, out, level, ns); err != nil {
424 return nil, err
425 }
426 q, sid := c.q, uint32(s.id)
427 s.layer.OnClosed(func() {
428 e := event{kind: evClosed, id: sid}
429 q.post(&e)
430 })
431 s.layer.OnConfigure(func(serial, w, h uint32) {
432 e := configureEvent(evLayerConfigure, sid, serial, w, h)
433 q.post(&e)
434 })
435 // Opposite anchors let the compositor assign that axis (size 0).
436 reqW, reqH := uint32(cfg.Width), uint32(cfg.Height)
437 if cfg.Anchors&(AnchorLeft|AnchorRight) == AnchorLeft|AnchorRight {
438 reqW = 0
439 }
440 if cfg.Anchors&(AnchorTop|AnchorBottom) == AnchorTop|AnchorBottom {
441 reqH = 0
442 }
443 var anchor uint32
444 for _, m := range [...][2]uint32{{uint32(AnchorTop), wlrlayershell.ANCHOR_TOP}, {uint32(AnchorBottom), wlrlayershell.ANCHOR_BOTTOM},
445 {uint32(AnchorLeft), wlrlayershell.ANCHOR_LEFT}, {uint32(AnchorRight), wlrlayershell.ANCHOR_RIGHT}} {
446 if uint32(cfg.Anchors)&m[0] != 0 {
447 anchor |= m[1]
448 }
449 }
450 for _, e := range []error{
451 s.layer.SetSize(reqW, reqH),
452 s.layer.SetAnchor(anchor),
453 s.layer.SetExclusiveZone(cfg.ExclusiveZone),
454 s.layer.SetMargin(cfg.Margin[0], cfg.Margin[1], cfg.Margin[2], cfg.Margin[3]),
455 s.layer.SetKeyboardInteractivity(uint32(cfg.Keyboard)),
456 } {
457 if e != nil {
458 return nil, e
459 }
460 }
461 if cfg.InputRects != nil {
462 if _, err = s.stageInputRects(cfg.InputRects); err != nil {
463 return nil, err
464 }
465 }
466 if err = s.finishSurface(); err != nil {
467 return nil, err
468 }
469 if err = s.surf.Commit(); err != nil { // initial empty commit asks for configure
470 return nil, err
471 }
472 ok = true
473 return s, nil
474}
475
476// NewToplevel creates an xdg-toplevel window with the given title and initial
477// logical size. It becomes presentable after the first Handler.Configure.
478func (c *Conn) NewToplevel(title string, w, h int32) (*Surface, error) {
479 if c.closed {
480 return nil, ErrClosed
481 }
482 if w <= 0 || h <= 0 {
483 return nil, fmt.Errorf("neferclient: invalid logical size %dx%d", w, h)
484 }
485 if err := c.ensureXdg(); err != nil {
486 return nil, err
487 }
488 c.pause()
489 defer c.resume()
490 s, err := c.newSurface(roleToplevel, w, h)
491 if err != nil {
492 return nil, err
493 }
494 ok := false
495 defer func() {
496 if !ok {
497 s.destroy()
498 }
499 }()
500 if s.xdg, err = c.g.xdg.GetXdgSurface(s.surf); err != nil {
501 return nil, err
502 }
503 if s.toplevel, err = s.xdg.GetToplevel(); err != nil {
504 return nil, err
505 }
506 q, sid := c.q, uint32(s.id)
507 s.toplevel.OnClose(func() {
508 e := event{kind: evClosed, id: sid}
509 q.post(&e)
510 })
511 s.toplevel.OnConfigure(func(w, h int32, _ []byte) {
512 e := event{kind: evXdgToplevel, id: sid, a: w, b: h}
513 q.post(&e)
514 })
515 s.xdg.OnConfigure(func(serial uint32) {
516 e := event{kind: evXdgConfigure, id: sid, serial: serial}
517 q.post(&e)
518 })
519 if err = s.toplevel.SetTitle(title); err != nil {
520 return nil, err
521 }
522 if err = s.finishSurface(); err != nil {
523 return nil, err
524 }
525 if err = s.surf.Commit(); err != nil {
526 return nil, err
527 }
528 ok = true
529 return s, nil
530}
531
532// applySurface handles every surface, lock and xdg_wm_base event on the owner.
533func (c *Conn) applySurface(ev *event, h Handler) {
534 defer ev.closeFD()
535 switch ev.kind {
536 case evPing:
537 if c.g.xdg != nil {
538 if err := c.g.xdg.Pong(ev.serial); err != nil {
539 h.Error(err)
540 }
541 }
542 return
543 case evLocked, evLockFinished:
544 l := c.curLock
545 if l == nil || l.gen != ev.id {
546 return
547 }
548 if ev.kind == evLocked {
549 l.locked = true
550 h.Locked()
551 } else {
552 l.finished = true
553 h.LockFinished()
554 }
555 return
556 }
557 s := c.surfaces[SurfaceID(ev.id)]
558 if s == nil || s.closed {
559 return // closed while the event was queued
560 }
561 id := s.id
562 switch ev.kind {
563 case evBadEvent:
564 h.Error(errors.New(badEventErrors[ev.a]))
565 case evClosed:
566 h.Closed(id)
567 case evXdgToplevel:
568 s.pendW, s.pendH = ev.a, ev.b
569 case evXdgConfigure:
570 if s.pendW > 0 && s.pendH > 0 {
571 s.width, s.height = s.pendW, s.pendH
572 }
573 s.configure(s.xdg.AckConfigure(ev.serial), h)
574 case evLayerConfigure:
575 if ev.a > 0 {
576 s.width = ev.a
577 }
578 if ev.b > 0 {
579 s.height = ev.b
580 }
581 s.configure(s.layer.AckConfigure(ev.serial), h)
582 case evLockConfigure:
583 s.width, s.height = ev.a, ev.b
584 s.configure(s.lockSurf.AckConfigure(ev.serial), h)
585 case evFrame:
586 s.frameReady = true
587 h.Frame(id)
588 case evPreferredScale:
589 if ev.a <= 0 {
590 h.Error(errors.New("neferclient: invalid fractional scale zero"))
591 return
592 }
593 s.setScale(float64(ev.a)/120, h)
594 case evBufferScale:
595 if s.frac == nil && ev.a > 0 {
596 s.setScale(float64(ev.a), h)
597 }
598 case evFeedbackTable:
599 fd := int(ev.fd)
600 ev.fd = -1 // readTable consumes fd; the deferred closeFD must not touch the number again
601 if err := s.readTable(fd, ev.flags); err != nil {
602 h.Error(err)
603 }
604 case evFeedbackMainDevice:
605 s.pending.MainDevice = ev.dev
606 case evFeedbackFormats:
607 b := ev.nameBytes()
608 for i := 0; i+1 < len(b); i += 2 {
609 idx := int(binary.NativeEndian.Uint16(b[i:]))
610 if idx >= len(s.table) {
611 h.Error(fmt.Errorf("neferclient: dmabuf format table index %d out of range", idx))
612 continue
613 }
614 s.pending.Formats = append(s.pending.Formats, s.table[idx])
615 }
616 case evFeedbackDone:
617 s.fb, s.pending = s.pending, Feedback{Formats: s.fb.Formats[:0]}
618 s.fbDone = true
619 h.FeedbackDone(id)
620 }
621}
622
623func (s *Surface) configure(ackErr error, h Handler) {
624 if ackErr != nil {
625 h.Error(ackErr)
626 return
627 }
628 if !s.configured {
629 s.configured, s.frameReady = true, true
630 }
631 h.Configure(s.id, s.width, s.height)
632}
633
634func (s *Surface) setScale(v float64, h Handler) {
635 if v == s.scale {
636 return
637 }
638 s.scale = v
639 h.Scale(s.id, v)
640}
641
642// readTable snapshots the format table with pread (no mapping of
643// compositor-owned memory) and always consumes fd.
644func (s *Surface) readTable(fd int, size uint32) error {
645 defer unix.Close(fd)
646 if size%16 != 0 || size > 16<<20 {
647 return fmt.Errorf("neferclient: invalid dmabuf format table size %d", size)
648 }
649 c := s.c
650 c.tableBuf = slices.Grow(c.tableBuf[:0], int(size))[:size]
651 b := c.tableBuf
652 for off := 0; off < len(b); {
653 n, err := unix.Pread(fd, b[off:], int64(off))
654 if err == unix.EINTR {
655 continue
656 }
657 if err != nil {
658 return fmt.Errorf("neferclient: read dmabuf format table: %w", err)
659 }
660 if n == 0 {
661 return errors.New("neferclient: truncated dmabuf format table")
662 }
663 off += n
664 }
665 s.table = s.table[:0]
666 for i := 0; i < len(b); i += 16 {
667 s.table = append(s.table, Format{binary.NativeEndian.Uint32(b[i:]), binary.NativeEndian.Uint64(b[i+8:])})
668 }
669 return nil
670}
671
672// SetInputRegion sets the input region (logical pixels). nil restores the
673// whole surface; a non-nil empty slice passes all input through. The region is
674// double-buffered state: it is committed immediately only once the surface
675// presented a buffer; before that the next Present applies it (committing
676// earlier would map a surface without a buffer, which lock surfaces forbid and
677// layer surfaces answer with an error). Lock surfaces always take all input
678// and refuse it.
679func (s *Surface) SetInputRegion(rects []Rect) error {
680 if s.closed {
681 return ErrClosed
682 }
683 if s.role == roleLock {
684 return errors.New("neferclient: lock surfaces take all input")
685 }
686 changed, err := s.stageInputRects(rects)
687 if err != nil || !changed || !s.presented {
688 return err
689 }
690 return s.surf.Commit()
691}
692
693func (s *Surface) stageInputRects(rects []Rect) (bool, error) {
694 if err := validateRects(rects); err != nil {
695 return false, err
696 }
697 if (rects != nil) == s.inputCustom && slices.Equal(rects, s.inputRects) {
698 return false, nil
699 }
700 if rects == nil {
701 if err := s.surf.SetInputRegion(nil); err != nil {
702 return false, err
703 }
704 } else {
705 region, err := s.c.g.compositor.CreateRegion()
706 if err != nil {
707 return false, err
708 }
709 for _, r := range rects {
710 if err = region.Add(r.X, r.Y, r.Width, r.Height); err != nil {
711 break
712 }
713 }
714 if err == nil {
715 err = s.surf.SetInputRegion(region)
716 }
717 _ = region.Destroy()
718 if err != nil {
719 return false, err
720 }
721 }
722 s.inputCustom = rects != nil
723 s.inputRects = append(s.inputRects[:0], rects...)
724 return true, nil
725}
726
727// Close destroys the surface, its buffers, timelines and role objects,
728// children before parents. It is safe to call more than once. The caller must
729// stop using buffers it imported first (the compositor keeps them until it
730// processes the destruction).
731func (s *Surface) Close() error {
732 if s == nil || s.closed {
733 return nil
734 }
735 if s.c.closed {
736 s.closed = true
737 return nil
738 }
739 return s.destroy()
740}
741
742func (s *Surface) destroy() error {
743 s.closed = true
744 delete(s.c.surfaces, s.id)
745 s.c.seatSurfaceGone(s)
746 var errs []error
747 add := func(err error) {
748 if err != nil {
749 errs = append(errs, err)
750 }
751 }
752 for id, b := range s.buffers {
753 add(b.buf.Destroy())
754 delete(s.buffers, id)
755 }
756 for id, t := range s.timelines {
757 add(t.Destroy())
758 delete(s.timelines, id)
759 }
760 if s.feedback != nil {
761 add(s.feedback.Destroy())
762 }
763 if s.sync != nil {
764 add(s.sync.Destroy())
765 }
766 if s.lockSurf != nil {
767 add(s.lockSurf.Destroy())
768 }
769 if s.layer != nil {
770 add(s.layer.Destroy())
771 }
772 if s.toplevel != nil {
773 add(s.toplevel.Destroy())
774 }
775 if s.xdg != nil {
776 add(s.xdg.Destroy())
777 }
778 if s.frac != nil {
779 add(s.frac.Destroy())
780 }
781 if s.viewport != nil {
782 add(s.viewport.Destroy())
783 }
784 if s.surf != nil {
785 add(s.surf.Destroy())
786 }
787 if s.cb != nil {
788 // A pending frame callback is owned by the compositor: abandon it so
789 // its late done and delete_id are discarded. An error only means no
790 // callback was pending.
791 _ = s.c.wlctx.Abandon(s.cb)
792 }
793 return errors.Join(errs...)
794}