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