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