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// Size returns the current logical size and scale (1 until announced). Before
166// the first configure the size is the requested one (zero for lock surfaces).
167func (s *Surface) Size() (w, h int32, scale float64) { return s.width, s.height, s.scale }
168
169// Feedback returns the latest complete dmabuf feedback, or nil before the
170// first Handler.FeedbackDone. The value and its slice stay valid until the
171// next FeedbackDone; use [Feedback.Clone] to keep it, and [Feedback.Equal] to
172// tell a changed preference from a repeated one.
173func (s *Surface) Feedback() *Feedback {
174 if !s.fbDone {
175 return nil
176 }
177 return &s.fb
178}
179
180// PhysicalSize returns the buffer size in pixels for the current logical size
181// and scale.
182func (s *Surface) PhysicalSize() (int32, int32, error) {
183 if s.scale <= 0 || s.width <= 0 || s.height <= 0 {
184 return 0, 0, errors.New("neferclient: invalid surface geometry")
185 }
186 if s.scale != math.Trunc(s.scale) && s.viewport == nil {
187 return 0, 0, &CapabilityError{Name: viewporter.WpViewporterInterface, Cause: fmt.Errorf("fractional scale %g requires viewporter", s.scale)}
188 }
189 // Ignore sub-nanopixel floating noise at exact integer boundaries.
190 x, y := math.Ceil(float64(s.width)*s.scale-1e-9), math.Ceil(float64(s.height)*s.scale-1e-9)
191 if x > math.MaxInt32 || y > math.MaxInt32 {
192 return 0, 0, errors.New("neferclient: buffer dimensions overflow")
193 }
194 return int32(x), int32(y), nil
195}
196
197func validateLayer(o *LayerConfig) error {
198 switch {
199 case o.Level < LayerBackground || o.Level > LayerOverlay:
200 return fmt.Errorf("neferclient: invalid layer level %d", o.Level)
201 case o.Anchors&^anchorAll != 0:
202 return fmt.Errorf("neferclient: invalid layer anchors %#x", uint8(o.Anchors))
203 case o.Keyboard > KeyboardOnDemand:
204 return fmt.Errorf("neferclient: invalid keyboard mode %d", o.Keyboard)
205 case o.ExclusiveZone < -1:
206 return fmt.Errorf("neferclient: invalid exclusive zone %d", o.ExclusiveZone)
207 case strings.ContainsRune(o.Namespace, 0), strings.ContainsRune(o.Output, 0):
208 return errors.New("neferclient: layer namespace or output contains NUL")
209 case o.Width < 0 || o.Height < 0:
210 return errors.New("neferclient: negative layer size")
211 }
212 if o.Anchors&(AnchorLeft|AnchorRight) != AnchorLeft|AnchorRight && o.Width == 0 ||
213 o.Anchors&(AnchorTop|AnchorBottom) != AnchorTop|AnchorBottom && o.Height == 0 {
214 return errors.New("neferclient: layer size required on axes not anchored to both edges")
215 }
216 return validateRects(o.InputRects)
217}
218
219func validateRects(rects []Rect) error {
220 for i, r := range rects {
221 if r.Width <= 0 || r.Height <= 0 {
222 return fmt.Errorf("neferclient: input rect %d has non-positive size %dx%d", i, r.Width, r.Height)
223 }
224 if int64(r.X)+int64(r.Width) > math.MaxInt32 || int64(r.Y)+int64(r.Height) > math.MaxInt32 {
225 return fmt.Errorf("neferclient: input rect %d overflows", i)
226 }
227 }
228 return nil
229}
230
231// newSurface creates the wl_surface and the add-on objects shared by every
232// role. The reader must be paused: handlers are installed after the requests.
233func (c *Conn) newSurface(role surfaceRole, w, h int32) (*Surface, error) {
234 if c.closed {
235 return nil, ErrClosed
236 }
237 if err := c.ensureCore(); err != nil {
238 return nil, err
239 }
240 c.ensureSeat() // optional: input flows to the Handler once the seat is bound
241 g := &c.g
242 c.nextSurf++
243 s := &Surface{c: c, id: c.nextSurf, role: role, width: w, height: h, scale: 1, sentBufScale: 1,
244 buffers: map[uint64]importedBuffer{}, timelines: map[uint64]*linuxdrmsyncobj.WpLinuxDrmSyncobjTimeline{}}
245 ok := false
246 defer func() {
247 if !ok {
248 s.destroy()
249 }
250 }()
251 var err error
252 if s.surf, err = g.compositor.CreateSurface(); err != nil {
253 return nil, err
254 }
255 q, sid := c.q, uint32(s.id)
256 s.surf.OnPreferredBufferScale(func(f int32) {
257 e := event{kind: evBufferScale, id: sid, a: f}
258 q.post(&e)
259 })
260 if g.viewporter != nil {
261 if s.viewport, err = g.viewporter.GetViewport(s.surf); err != nil {
262 return nil, err
263 }
264 }
265 if g.fscale != nil {
266 if s.frac, err = g.fscale.GetFractionalScale(s.surf); err != nil {
267 return nil, err
268 }
269 s.frac.OnPreferredScale(func(n uint32) {
270 e := event{kind: evPreferredScale, id: sid, a: int32(n)}
271 q.post(&e)
272 })
273 }
274 // The role objects come next (callers); sync surface and feedback after.
275 s.cb = &frameCallback{q: q, id: s.id}
276 s.cb.SetContext(c.wlctx)
277 s.req = wireRequests{s: s}
278 c.surfaces[s.id] = s
279 ok = true
280 return s, nil
281}
282
283// finishSurface creates the explicit-sync and feedback objects once the role
284// exists.
285func (s *Surface) finishSurface() error {
286 c := s.c
287 var err error
288 if s.sync, err = c.g.syncobj.GetSurface(s.surf); err != nil {
289 return err
290 }
291 if s.feedback, err = c.g.dmabuf.GetDefaultFeedback(); err != nil {
292 return err
293 }
294 s.watchFeedback()
295 return nil
296}
297
298func (s *Surface) watchFeedback() {
299 q, sid := s.c.q, uint32(s.id)
300 s.feedback.OnFormatTable(func(fd *wl.OwnedFD, size uint32) {
301 n, err := fd.Take()
302 if err != nil {
303 e := event{kind: evBadEvent, id: sid, a: badFeedbackFD}
304 q.post(&e)
305 return
306 }
307 e := event{kind: evFeedbackTable, id: sid, fd: int32(n), flags: size}
308 q.post(&e)
309 })
310 s.feedback.OnMainDevice(func(dev []byte) {
311 if len(dev) != 8 {
312 e := event{kind: evBadEvent, id: sid, a: badMainDevice}
313 q.post(&e)
314 return
315 }
316 e := event{kind: evFeedbackMainDevice, id: sid, dev: binary.NativeEndian.Uint64(dev)}
317 q.post(&e)
318 })
319 s.feedback.OnTrancheFormats(func(data []byte) {
320 if len(data)%2 != 0 {
321 e := event{kind: evBadEvent, id: sid, a: badTranche}
322 q.post(&e)
323 return
324 }
325 for len(data) > 0 { // chunks of up to maxNameLen/2 indices
326 n := min(len(data), maxNameLen)
327 e := event{kind: evFeedbackFormats, id: sid}
328 e.setBytes(data[:n])
329 q.post(&e)
330 data = data[n:]
331 }
332 })
333 s.feedback.OnDone(func() {
334 e := event{kind: evFeedbackDone, id: sid}
335 q.post(&e)
336 })
337}
338
339// Codes of evBadEvent.
340const (
341 badFeedbackFD int32 = iota + 1
342 badMainDevice
343 badTranche
344 badConfigure
345)
346
347var badEventErrors = [...]string{
348 badFeedbackFD: "neferclient: dmabuf format table descriptor unavailable",
349 badMainDevice: "neferclient: invalid dmabuf main_device length",
350 badTranche: "neferclient: odd dmabuf tranche format list",
351 badConfigure: "neferclient: configure size out of range",
352}
353
354// configureEvent builds a layer or lock configure event, or a badConfigure
355// event when the compositor announced an out-of-range size.
356func configureEvent(kind eventKind, sid, serial, w, h uint32) event {
357 if w > maxLayerDimension || h > maxLayerDimension {
358 return event{kind: evBadEvent, id: sid, a: badConfigure}
359 }
360 return event{kind: kind, id: sid, serial: serial, a: int32(w), b: int32(h)}
361}
362
363// NewLayerSurface creates a wlr-layer-shell surface. It becomes presentable
364// after the first Handler.Configure.
365func (c *Conn) NewLayerSurface(cfg LayerConfig) (*Surface, error) {
366 if c.closed {
367 return nil, ErrClosed
368 }
369 if err := validateLayer(&cfg); err != nil {
370 return nil, err
371 }
372 if err := c.ensureLayerShell(); err != nil {
373 return nil, err
374 }
375 if cfg.Keyboard == KeyboardOnDemand && c.g.layerVersion < 4 {
376 return nil, &CapabilityError{Name: wlrlayershell.LayerShellInterface, Cause: fmt.Errorf("on-demand keyboard requires version 4, negotiated %d", c.g.layerVersion)}
377 }
378 var out *wayland.Output
379 if cfg.Output != "" {
380 for _, o := range c.outputs {
381 if o.done && o.pub.Name == cfg.Output {
382 out = o.proxy
383 break
384 }
385 }
386 if out == nil {
387 return nil, &CapabilityError{Name: wayland.OutputInterface, Cause: fmt.Errorf("output %q not found", cfg.Output)}
388 }
389 }
390 c.pause()
391 defer c.resume()
392 s, err := c.newSurface(roleLayer, cfg.Width, cfg.Height)
393 if err != nil {
394 return nil, err
395 }
396 ok := false
397 defer func() {
398 if !ok {
399 s.destroy()
400 }
401 }()
402 ns := cfg.Namespace
403 if ns == "" {
404 ns = DefaultLayerNamespace
405 }
406 level := [...]uint32{LayerBackground: wlrlayershell.LAYER_BACKGROUND, LayerBottom: wlrlayershell.LAYER_BOTTOM,
407 LayerTop: wlrlayershell.LAYER_TOP, LayerOverlay: wlrlayershell.LAYER_OVERLAY}[cfg.Level]
408 if s.layer, err = c.g.layer.GetLayerSurface(s.surf, out, level, ns); err != nil {
409 return nil, err
410 }
411 q, sid := c.q, uint32(s.id)
412 s.layer.OnClosed(func() {
413 e := event{kind: evClosed, id: sid}
414 q.post(&e)
415 })
416 s.layer.OnConfigure(func(serial, w, h uint32) {
417 e := configureEvent(evLayerConfigure, sid, serial, w, h)
418 q.post(&e)
419 })
420 // Opposite anchors let the compositor assign that axis (size 0).
421 reqW, reqH := uint32(cfg.Width), uint32(cfg.Height)
422 if cfg.Anchors&(AnchorLeft|AnchorRight) == AnchorLeft|AnchorRight {
423 reqW = 0
424 }
425 if cfg.Anchors&(AnchorTop|AnchorBottom) == AnchorTop|AnchorBottom {
426 reqH = 0
427 }
428 var anchor uint32
429 for _, m := range [...][2]uint32{{uint32(AnchorTop), wlrlayershell.ANCHOR_TOP}, {uint32(AnchorBottom), wlrlayershell.ANCHOR_BOTTOM},
430 {uint32(AnchorLeft), wlrlayershell.ANCHOR_LEFT}, {uint32(AnchorRight), wlrlayershell.ANCHOR_RIGHT}} {
431 if uint32(cfg.Anchors)&m[0] != 0 {
432 anchor |= m[1]
433 }
434 }
435 for _, e := range []error{
436 s.layer.SetSize(reqW, reqH),
437 s.layer.SetAnchor(anchor),
438 s.layer.SetExclusiveZone(cfg.ExclusiveZone),
439 s.layer.SetMargin(cfg.Margin[0], cfg.Margin[1], cfg.Margin[2], cfg.Margin[3]),
440 s.layer.SetKeyboardInteractivity(uint32(cfg.Keyboard)),
441 } {
442 if e != nil {
443 return nil, e
444 }
445 }
446 if cfg.InputRects != nil {
447 if _, err = s.stageInputRects(cfg.InputRects); err != nil {
448 return nil, err
449 }
450 }
451 if err = s.finishSurface(); err != nil {
452 return nil, err
453 }
454 if err = s.surf.Commit(); err != nil { // initial empty commit asks for configure
455 return nil, err
456 }
457 ok = true
458 return s, nil
459}
460
461// NewToplevel creates an xdg-toplevel window with the given title and initial
462// logical size. It becomes presentable after the first Handler.Configure.
463func (c *Conn) NewToplevel(title string, w, h int32) (*Surface, error) {
464 if c.closed {
465 return nil, ErrClosed
466 }
467 if w <= 0 || h <= 0 {
468 return nil, fmt.Errorf("neferclient: invalid logical size %dx%d", w, h)
469 }
470 if err := c.ensureXdg(); err != nil {
471 return nil, err
472 }
473 c.pause()
474 defer c.resume()
475 s, err := c.newSurface(roleToplevel, w, h)
476 if err != nil {
477 return nil, err
478 }
479 ok := false
480 defer func() {
481 if !ok {
482 s.destroy()
483 }
484 }()
485 if s.xdg, err = c.g.xdg.GetXdgSurface(s.surf); err != nil {
486 return nil, err
487 }
488 if s.toplevel, err = s.xdg.GetToplevel(); err != nil {
489 return nil, err
490 }
491 q, sid := c.q, uint32(s.id)
492 s.toplevel.OnClose(func() {
493 e := event{kind: evClosed, id: sid}
494 q.post(&e)
495 })
496 s.toplevel.OnConfigure(func(w, h int32, _ []byte) {
497 e := event{kind: evXdgToplevel, id: sid, a: w, b: h}
498 q.post(&e)
499 })
500 s.xdg.OnConfigure(func(serial uint32) {
501 e := event{kind: evXdgConfigure, id: sid, serial: serial}
502 q.post(&e)
503 })
504 if err = s.toplevel.SetTitle(title); err != nil {
505 return nil, err
506 }
507 if err = s.finishSurface(); err != nil {
508 return nil, err
509 }
510 if err = s.surf.Commit(); err != nil {
511 return nil, err
512 }
513 ok = true
514 return s, nil
515}
516
517// applySurface handles every surface, lock and xdg_wm_base event on the owner.
518func (c *Conn) applySurface(ev *event, h Handler) {
519 defer ev.closeFD()
520 switch ev.kind {
521 case evPing:
522 if c.g.xdg != nil {
523 if err := c.g.xdg.Pong(ev.serial); err != nil {
524 h.Error(err)
525 }
526 }
527 return
528 case evLocked, evLockFinished:
529 l := c.curLock
530 if l == nil || l.gen != ev.id {
531 return
532 }
533 if ev.kind == evLocked {
534 l.locked = true
535 h.Locked()
536 } else {
537 l.finished = true
538 h.LockFinished()
539 }
540 return
541 }
542 s := c.surfaces[SurfaceID(ev.id)]
543 if s == nil || s.closed {
544 return // closed while the event was queued
545 }
546 id := s.id
547 switch ev.kind {
548 case evBadEvent:
549 h.Error(errors.New(badEventErrors[ev.a]))
550 case evClosed:
551 h.Closed(id)
552 case evXdgToplevel:
553 s.pendW, s.pendH = ev.a, ev.b
554 case evXdgConfigure:
555 if s.pendW > 0 && s.pendH > 0 {
556 s.width, s.height = s.pendW, s.pendH
557 }
558 s.configure(s.xdg.AckConfigure(ev.serial), h)
559 case evLayerConfigure:
560 if ev.a > 0 {
561 s.width = ev.a
562 }
563 if ev.b > 0 {
564 s.height = ev.b
565 }
566 s.configure(s.layer.AckConfigure(ev.serial), h)
567 case evLockConfigure:
568 s.width, s.height = ev.a, ev.b
569 s.configure(s.lockSurf.AckConfigure(ev.serial), h)
570 case evFrame:
571 s.frameReady = true
572 h.Frame(id)
573 case evPreferredScale:
574 if ev.a <= 0 {
575 h.Error(errors.New("neferclient: invalid fractional scale zero"))
576 return
577 }
578 s.setScale(float64(ev.a)/120, h)
579 case evBufferScale:
580 if s.frac == nil && ev.a > 0 {
581 s.setScale(float64(ev.a), h)
582 }
583 case evFeedbackTable:
584 fd := int(ev.fd)
585 ev.fd = -1 // readTable consumes fd; the deferred closeFD must not touch the number again
586 if err := s.readTable(fd, ev.flags); err != nil {
587 h.Error(err)
588 }
589 case evFeedbackMainDevice:
590 s.pending.MainDevice = ev.dev
591 case evFeedbackFormats:
592 b := ev.nameBytes()
593 for i := 0; i+1 < len(b); i += 2 {
594 idx := int(binary.NativeEndian.Uint16(b[i:]))
595 if idx >= len(s.table) {
596 h.Error(fmt.Errorf("neferclient: dmabuf format table index %d out of range", idx))
597 continue
598 }
599 s.pending.Formats = append(s.pending.Formats, s.table[idx])
600 }
601 case evFeedbackDone:
602 s.fb, s.pending = s.pending, Feedback{Formats: s.fb.Formats[:0]}
603 s.fbDone = true
604 h.FeedbackDone(id)
605 }
606}
607
608func (s *Surface) configure(ackErr error, h Handler) {
609 if ackErr != nil {
610 h.Error(ackErr)
611 return
612 }
613 if !s.configured {
614 s.configured, s.frameReady = true, true
615 }
616 h.Configure(s.id, s.width, s.height)
617}
618
619func (s *Surface) setScale(v float64, h Handler) {
620 if v == s.scale {
621 return
622 }
623 s.scale = v
624 h.Scale(s.id, v)
625}
626
627// readTable snapshots the format table with pread (no mapping of
628// compositor-owned memory) and always consumes fd.
629func (s *Surface) readTable(fd int, size uint32) error {
630 defer unix.Close(fd)
631 if size%16 != 0 || size > 16<<20 {
632 return fmt.Errorf("neferclient: invalid dmabuf format table size %d", size)
633 }
634 c := s.c
635 c.tableBuf = slices.Grow(c.tableBuf[:0], int(size))[:size]
636 b := c.tableBuf
637 for off := 0; off < len(b); {
638 n, err := unix.Pread(fd, b[off:], int64(off))
639 if err == unix.EINTR {
640 continue
641 }
642 if err != nil {
643 return fmt.Errorf("neferclient: read dmabuf format table: %w", err)
644 }
645 if n == 0 {
646 return errors.New("neferclient: truncated dmabuf format table")
647 }
648 off += n
649 }
650 s.table = s.table[:0]
651 for i := 0; i < len(b); i += 16 {
652 s.table = append(s.table, Format{binary.NativeEndian.Uint32(b[i:]), binary.NativeEndian.Uint64(b[i+8:])})
653 }
654 return nil
655}
656
657// SetInputRegion sets the input region (logical pixels). nil restores the
658// whole surface; a non-nil empty slice passes all input through. The region is
659// double-buffered state: it is committed immediately only once the surface
660// presented a buffer; before that the next Present applies it (committing
661// earlier would map a surface without a buffer, which lock surfaces forbid and
662// layer surfaces answer with an error). Lock surfaces always take all input
663// and refuse it.
664func (s *Surface) SetInputRegion(rects []Rect) error {
665 if s.closed {
666 return ErrClosed
667 }
668 if s.role == roleLock {
669 return errors.New("neferclient: lock surfaces take all input")
670 }
671 changed, err := s.stageInputRects(rects)
672 if err != nil || !changed || !s.presented {
673 return err
674 }
675 return s.surf.Commit()
676}
677
678func (s *Surface) stageInputRects(rects []Rect) (bool, error) {
679 if err := validateRects(rects); err != nil {
680 return false, err
681 }
682 if (rects != nil) == s.inputCustom && slices.Equal(rects, s.inputRects) {
683 return false, nil
684 }
685 if rects == nil {
686 if err := s.surf.SetInputRegion(nil); err != nil {
687 return false, err
688 }
689 } else {
690 region, err := s.c.g.compositor.CreateRegion()
691 if err != nil {
692 return false, err
693 }
694 for _, r := range rects {
695 if err = region.Add(r.X, r.Y, r.Width, r.Height); err != nil {
696 break
697 }
698 }
699 if err == nil {
700 err = s.surf.SetInputRegion(region)
701 }
702 _ = region.Destroy()
703 if err != nil {
704 return false, err
705 }
706 }
707 s.inputCustom = rects != nil
708 s.inputRects = append(s.inputRects[:0], rects...)
709 return true, nil
710}
711
712// Close destroys the surface, its buffers, timelines and role objects,
713// children before parents. It is safe to call more than once. The caller must
714// stop using buffers it imported first (the compositor keeps them until it
715// processes the destruction).
716func (s *Surface) Close() error {
717 if s == nil || s.closed {
718 return nil
719 }
720 if s.c.closed {
721 s.closed = true
722 return nil
723 }
724 return s.destroy()
725}
726
727func (s *Surface) destroy() error {
728 s.closed = true
729 delete(s.c.surfaces, s.id)
730 s.c.seatSurfaceGone(s)
731 var errs []error
732 add := func(err error) {
733 if err != nil {
734 errs = append(errs, err)
735 }
736 }
737 for id, b := range s.buffers {
738 add(b.buf.Destroy())
739 delete(s.buffers, id)
740 }
741 for id, t := range s.timelines {
742 add(t.Destroy())
743 delete(s.timelines, id)
744 }
745 if s.feedback != nil {
746 add(s.feedback.Destroy())
747 }
748 if s.sync != nil {
749 add(s.sync.Destroy())
750 }
751 if s.lockSurf != nil {
752 add(s.lockSurf.Destroy())
753 }
754 if s.layer != nil {
755 add(s.layer.Destroy())
756 }
757 if s.toplevel != nil {
758 add(s.toplevel.Destroy())
759 }
760 if s.xdg != nil {
761 add(s.xdg.Destroy())
762 }
763 if s.frac != nil {
764 add(s.frac.Destroy())
765 }
766 if s.viewport != nil {
767 add(s.viewport.Destroy())
768 }
769 if s.surf != nil {
770 add(s.surf.Destroy())
771 }
772 if s.cb != nil {
773 // A pending frame callback is owned by the compositor: abandon it so
774 // its late done and delete_id are discarded. An error only means no
775 // callback was pending.
776 _ = s.c.wlctx.Abandon(s.cb)
777 }
778 return errors.Join(errs...)
779}