1//go:build linux
2
3// Package wayland owns the minimal xdg-shell window and presentation protocol
4// objects. Vulkan allocations and DMA-BUF image creation remain the renderer's
5// responsibility; a window may not attach until the first configure is acked.
6package wayland
7
8import (
9 "context"
10 "encoding/binary"
11 "errors"
12 "fmt"
13 "math"
14 "os"
15 "path/filepath"
16 "sync"
17 "sync/atomic"
18 "time"
19
20 "github.com/bnema/wlturbo"
21 "github.com/bnema/wlturbo/protocol/core"
22 "github.com/bnema/wlturbo/protocol/cursorshape"
23 "github.com/bnema/wlturbo/protocol/drmsyncobj"
24 "github.com/bnema/wlturbo/protocol/extsessionlock"
25 "github.com/bnema/wlturbo/protocol/fractionalscale"
26 "github.com/bnema/wlturbo/protocol/linuxdmabuf"
27 "github.com/bnema/wlturbo/protocol/viewporter"
28 "github.com/bnema/wlturbo/protocol/xdgshell"
29 "github.com/bnema/wlturbo/wl"
30
31 "github.com/bnema/nefergui/internal/platform/wayland/layershell"
32)
33
34// CapabilityError reports a missing or insufficient required protocol.
35type CapabilityError struct {
36 Name string
37 Cause error
38}
39
40func (e *CapabilityError) Error() string {
41 return fmt.Sprintf("Wayland capability %s: %v", e.Name, e.Cause)
42}
43func (e *CapabilityError) Unwrap() error { return e.Cause }
44
45const (
46 DRMXRGB8888 = uint32(0x34325258)
47 DRMARGB8888 = uint32(0x34325241)
48)
49
50type Window struct {
51 connection *Connection
52 ownsConnection bool
53 LockSurface *extsessionlock.ExtSessionLockSurface
54 Display *wl.Display
55 Compositor *core.Compositor
56 Shell *xdgshell.XdgWmBase
57 Dmabuf *linuxdmabuf.LinuxDmabuf
58 SyncManager *drmsyncobj.WpLinuxDrmSyncobjManager
59 LayerShell *layershell.LayerShell // nil for xdg windows
60 LayerSurface *layershell.LayerSurface // nil for xdg windows
61 Surface *core.Surface
62 XdgSurface *xdgshell.XdgSurface
63 Toplevel *xdgshell.XdgToplevel
64 SyncSurface *drmsyncobj.WpLinuxDrmSyncobjSurface
65 Viewporter *viewporter.WpViewporter
66 Viewport *viewporter.WpViewport
67 ScaleManager *fractionalscale.WpFractionalScaleManager
68 Fractional *fractionalscale.WpFractionalScale
69 Feedback *linuxdmabuf.LinuxDmabufFeedback
70 Seat *core.Seat
71 Pointer *core.Pointer
72 CursorShapes *cursorshape.WpCursorShapeManager // optional
73 cursor cursorState
74 pointerGen uint64 // bumped per wl_pointer; session loop only
75 WLKeyboard *core.Keyboard
76 keymapCache *keyboardMapCache // owner retires each keyboard generation
77 seatName, seatVersion uint32
78 Width, Height int32
79 Scale float64
80 Configured, Closed, Dirty bool
81 Transparent bool
82 FrameReady bool
83 // Feedback fields are populated by the default feedback event stream.
84 FeedbackDone bool // owner has applied the complete feedback round
85 MainDevice uint64
86 Formats []linuxdmabuf.FormatEntry
87 Tranches [][]linuxdmabuf.FormatEntry
88 feedbackErr error
89 events chan Event
90 readerDone chan struct{}
91 readerStop chan struct{}
92 readerOnce sync.Once
93 readerStarted atomic.Bool
94 rawInput []Event // lock-only numeric protocol events, never ordinary text
95 inputAdapter *InputAdapter
96 inputQueue []InputEvent
97 // inputRects/inputCustom record the current input region; session loop only.
98 inputRects []Rect
99 inputCustom bool
100 layerOutputGlobal uint32
101 lockOutput uint32
102 layerOutputSelected bool
103 // InputOverflow counts events discarded when the 4096-event queue fills.
104 InputOverflow uint64
105}
106
107// Connect opens a default xdg-toplevel window with full-surface input. It
108// cannot be cancelled; use ConnectWithOptions for a cancellable startup.
109func Connect(name string, width, height int32, transparent bool) (*Window, error) {
110 return ConnectWithOptions(context.Background(), name, width, height, transparent, SurfaceOptions{})
111}
112
113// dialTimeout bounds the unix-socket dial; a local dial is normally instant.
114const dialTimeout = 5 * time.Second
115
116// waylandSocketPath resolves name like wlturbo.Connect does.
117func waylandSocketPath(name string) (string, error) {
118 if name == "" {
119 name = os.Getenv("WAYLAND_DISPLAY")
120 if name == "" {
121 name = "wayland-0"
122 }
123 }
124 if filepath.IsAbs(name) {
125 return name, nil
126 }
127 runDir := os.Getenv("XDG_RUNTIME_DIR")
128 if runDir == "" {
129 return "", errors.New("XDG_RUNTIME_DIR not set")
130 }
131 return filepath.Join(runDir, name), nil
132}
133
134// ConnectWithOptions opens a window whose role and initial input region are
135// selected by opts. The zero SurfaceOptions is identical to Connect. Startup
136// (dial, registry, configure, dmabuf feedback) is abandoned with ctx: the
137// connection is closed and the returned error wraps ctx.Err(). Once it
138// returns successfully, ctx no longer affects the window.
139func ConnectWithOptions(ctx context.Context, name string, width, height int32, transparent bool, opts SurfaceOptions) (*Window, error) {
140 if ctx == nil {
141 return nil, errors.New("nil context")
142 }
143 if width <= 0 || height <= 0 {
144 return nil, fmt.Errorf("invalid logical size %dx%d", width, height)
145 }
146 if err := opts.Validate(); err != nil {
147 return nil, err
148 }
149 c, err := ConnectConnection(ctx, name)
150 if err != nil {
151 return nil, err
152 }
153 w, err := c.NewWindow(ctx, width, height, transparent, opts)
154 if err != nil {
155 _ = c.Close()
156 return nil, err
157 }
158 w.ownsConnection = true
159 return w, nil
160}
161
162// NewWindow creates a sibling on this connection. Before StartReader it waits
163// for initial configure/feedback; afterwards it returns unconfigured and the
164// owner must Apply tagged events before allocating/presenting buffers.
165func (c *Connection) NewWindow(ctx context.Context, width, height int32, transparent bool, opts SurfaceOptions) (res *Window, err error) {
166 if ctx == nil {
167 return nil, errors.New("nil context")
168 }
169 if width <= 0 || height <= 0 {
170 return nil, fmt.Errorf("invalid logical size %dx%d", width, height)
171 }
172 if err = opts.Validate(); err != nil {
173 return nil, err
174 }
175 if err = ctx.Err(); err != nil {
176 return nil, err
177 }
178 if c.closed {
179 return nil, errors.New("connection closed")
180 }
181 if opts.Lock != nil && (transparent || opts.Layer != nil || opts.InputRects != nil) {
182 return nil, errors.New("lock role requires opaque full-input surface")
183 }
184 if opts.Lock != nil && !c.started.Load() {
185 // A lock that was already refused or ended must not get surfaces; its
186 // lock and output events are held for the owner, not applied here.
187 if err = c.drainStartup(true); err != nil {
188 return nil, err
189 }
190 }
191 // Objects created below get their handlers after their request is sent;
192 // a running reader must not dispatch their first events in between.
193 defer c.pauseReader()()
194 d := c.Display
195 // Closing the display unblocks any pending read; Display.Close is idempotent.
196 stopWatch := context.AfterFunc(ctx, func() { _ = d.Close() })
197 w := &Window{connection: c, events: c.events, readerStop: c.stop, Display: d, Width: width, Height: height, Scale: 1, Transparent: transparent}
198 w.readerStarted.Store(c.started.Load())
199 defer func() {
200 if !stopWatch() && err == nil {
201 // Cancelled after the last step but before return: the watcher owns a
202 // closed display, so the window is unusable.
203 err = ctx.Err()
204 }
205 if err != nil {
206 if cerr := ctx.Err(); cerr != nil && !errors.Is(err, cerr) {
207 err = errors.Join(cerr, err)
208 }
209 _ = w.Close()
210 res = nil
211 }
212 }()
213 wctx := d.Context()
214 bind := func(name string, supported, minimum uint32, proxy wl.Proxy) error {
215 advertised, ok := d.Registry().FindGlobal(name)
216 if !ok || advertised.Version < minimum {
217 return &CapabilityError{Name: name, Cause: fmt.Errorf("requires version %d, announced %d", minimum, advertised.Version)}
218 }
219 got, e := d.Registry().BindNegotiated(name, supported, proxy)
220 if e != nil {
221 return &CapabilityError{Name: name, Cause: e}
222 }
223 if got < minimum {
224 return &CapabilityError{Name: name, Cause: fmt.Errorf("requires version %d, compositor negotiated %d", minimum, got)}
225 }
226 return nil
227 }
228 w.Compositor = core.NewCompositor(wctx)
229 if err = bind(core.CompositorInterface, 6, 4, w.Compositor); err != nil {
230 return nil, err
231 }
232 if opts.Layer != nil {
233 if err = w.bindLayerShell(bind); err != nil {
234 return nil, err
235 }
236 } else if opts.Lock == nil {
237 w.Shell = xdgshell.NewXdgWmBase(wctx)
238 if err = bind(xdgshell.XdgWmBaseInterface, 6, 1, w.Shell); err != nil {
239 return nil, err
240 }
241 }
242 w.Dmabuf = linuxdmabuf.NewLinuxDmabuf(wctx)
243 if err = bind(linuxdmabuf.LinuxDmabufInterface, 4, 4, w.Dmabuf); err != nil {
244 return nil, err
245 }
246 w.SyncManager = drmsyncobj.NewWpLinuxDrmSyncobjManager(wctx)
247 if err = bind(drmsyncobj.WpLinuxDrmSyncobjManagerInterface, 1, 1, w.SyncManager); err != nil {
248 return nil, err
249 }
250 w.Viewporter = viewporter.NewWpViewporter(wctx)
251 if _, e := d.Registry().BindNegotiated(viewporter.WpViewporterInterface, 1, w.Viewporter); e != nil {
252 if !errors.Is(e, wlturbo.ErrGlobalNotFound) {
253 return nil, e
254 }
255 wctx.Unregister(w.Viewporter)
256 w.Viewporter = nil
257 }
258 w.ScaleManager = fractionalscale.NewWpFractionalScaleManager(wctx)
259 if _, e := d.Registry().BindNegotiated(fractionalscale.WpFractionalScaleManagerInterface, 1, w.ScaleManager); e != nil {
260 if !errors.Is(e, wlturbo.ErrGlobalNotFound) {
261 return nil, e
262 }
263 wctx.Unregister(w.ScaleManager)
264 w.ScaleManager = nil
265 }
266 w.CursorShapes = cursorshape.NewWpCursorShapeManager(wctx)
267 if _, e := d.Registry().BindNegotiated(cursorshape.WpCursorShapeManagerInterface, 1, w.CursorShapes); e != nil {
268 if !errors.Is(e, wlturbo.ErrGlobalNotFound) {
269 return nil, e
270 }
271 wctx.Unregister(w.CursorShapes)
272 w.CursorShapes = nil
273 }
274 w.Seat, w.Pointer, w.WLKeyboard = c.seat.Seat, c.seat.Pointer, c.seat.WLKeyboard
275 if opts.Lock != nil {
276 w.Seat = nil
277 } // clipboard/IME constructors require Seat
278 w.pointerGen = c.seat.pointerGen
279 if w.Shell != nil {
280 w.Shell.OnPing(func(serial uint32) {
281 if e := w.Shell.Pong(serial); e != nil {
282 w.feedbackFailure(e)
283 }
284 })
285 }
286 w.Surface, err = w.Compositor.CreateSurface()
287 if err != nil {
288 return nil, err
289 }
290 if w.Viewporter != nil {
291 w.Viewport, err = w.Viewporter.GetViewport(w.Surface)
292 if err != nil {
293 return nil, err
294 }
295 }
296 if w.ScaleManager != nil {
297 w.Fractional, err = w.ScaleManager.GetFractionalScale(w.Surface)
298 if err != nil {
299 return nil, err
300 }
301 w.Fractional.OnPreferredScale(func(scale uint32) {
302 if w.readerStarted.Load() {
303 w.post(Event{Kind: PreferredScale, Scale: scale})
304 return
305 }
306 if scale == 0 {
307 w.feedbackFailure(fmt.Errorf("invalid fractional scale zero"))
308 return
309 }
310 if scale%120 != 0 && w.Viewport == nil {
311 w.feedbackFailure(&CapabilityError{Name: viewporter.WpViewporterInterface, Cause: fmt.Errorf("fractional scale %d/120 requires viewporter", scale)})
312 return
313 }
314 if w.Scale != float64(scale)/120 {
315 w.Scale = float64(scale) / 120
316 w.Dirty = true
317 }
318 })
319 }
320 w.Surface.OnPreferredBufferScale(func(factor int32) {
321 if w.readerStarted.Load() {
322 w.post(Event{Kind: BufferScale, Factor: factor})
323 return
324 }
325 if w.Fractional == nil && factor > 0 && w.Scale != float64(factor) {
326 w.Scale = float64(factor)
327 w.Dirty = true
328 }
329 })
330 c.windows[w.Surface.ID()] = w
331 c.routes.Store(w.Surface.ID(), w)
332 if opts.Lock != nil {
333 if err = w.createLockSurface(*opts.Lock); err != nil {
334 return nil, err
335 }
336 } else if opts.Layer != nil {
337 if err = w.createLayerSurface(*opts.Layer); err != nil {
338 return nil, err
339 }
340 } else if err = w.createToplevel(); err != nil {
341 return nil, err
342 }
343 if opts.InputRects != nil {
344 if err = w.SetInputRects(opts.InputRects); err != nil {
345 return nil, err
346 }
347 }
348 w.SyncSurface, err = w.SyncManager.GetSurface(w.Surface)
349 if err != nil {
350 return nil, err
351 }
352 w.Feedback, err = w.Dmabuf.GetDefaultFeedback()
353 if err != nil {
354 return nil, err
355 }
356 w.watchFeedback()
357 // ext-session-lock configure is sent by get_lock_surface; unlike xdg and
358 // layer-shell it forbids the initial empty commit before ACK+buffer.
359 if opts.Lock == nil {
360 if err = w.Surface.Commit(); err != nil {
361 return nil, err
362 }
363 }
364 if c.started.Load() {
365 return w, nil
366 }
367 for !w.Configured || !w.FeedbackDone || w.MainDevice == 0 || len(w.Tranches) == 0 {
368 if w.Closed {
369 return nil, fmt.Errorf("window closed before initial configure and feedback")
370 }
371 if err = c.drainStartup(true); err != nil {
372 return nil, err // a finished already queued: do not wait for a configure
373 }
374 if err = w.Dispatch(); err != nil {
375 return nil, err
376 }
377 if err = c.drainStartup(true); err != nil {
378 return nil, err
379 }
380 }
381 return w, nil
382}
383
384func (w *Window) createToplevel() error {
385 var err error
386 w.XdgSurface, err = w.Shell.GetXdgSurface(w.Surface)
387 if err != nil {
388 return err
389 }
390 w.Toplevel, err = w.XdgSurface.GetToplevel()
391 if err != nil {
392 return err
393 }
394 w.Toplevel.OnClose(func() {
395 if w.readerStarted.Load() {
396 w.post(Event{Kind: CloseEvent})
397 return
398 }
399 w.Closed = true
400 })
401 w.Toplevel.OnConfigure(func(width, height int32, _ []byte) {
402 if w.readerStarted.Load() {
403 w.post(Event{Kind: ConfigureSize, Width: width, Height: height})
404 return
405 }
406 if width > 0 && height > 0 && (w.Width != width || w.Height != height) {
407 w.Width, w.Height, w.Dirty = width, height, true
408 }
409 })
410 w.XdgSurface.OnConfigure(func(serial uint32) {
411 if w.readerStarted.Load() {
412 w.post(Event{Kind: ConfigureSerial, Serial: serial})
413 return
414 }
415 if e := w.XdgSurface.AckConfigure(serial); e != nil {
416 w.feedbackFailure(e)
417 return
418 }
419 w.Configured, w.FrameReady = true, true
420 })
421 return nil
422}
423
424func (w *Window) Dispatch() error {
425 if w.connection != nil && w.connection.started.Load() {
426 return errors.New("connection reader already started")
427 }
428 if w.feedbackErr != nil {
429 return w.feedbackErr
430 }
431 if err := w.Display.Dispatch(); err != nil {
432 return err
433 }
434 return w.feedbackErr
435}
436
437// Ready reports that a buffer may be allocated and attached: the role's
438// configure was acknowledged, the dmabuf feedback round is complete and the
439// previous frame callback has fired.
440func (w *Window) Ready() bool { return w.Configured && w.FeedbackDone && w.FrameReady }
441
442// IsLock reports whether the window is an ext-session-lock surface.
443func (w *Window) IsLock() bool { return w != nil && w.LockSurface != nil }
444
445func (w *Window) PhysicalSize() (int32, int32, error) {
446 if w.Scale <= 0 || w.Width <= 0 || w.Height <= 0 {
447 return 0, 0, fmt.Errorf("invalid window geometry")
448 }
449 if w.Scale != math.Trunc(w.Scale) && w.Viewport == nil {
450 return 0, 0, &CapabilityError{Name: viewporter.WpViewporterInterface, Cause: fmt.Errorf("fractional scale %g requires viewporter", w.Scale)}
451 }
452 // Ignore sub-nanopixel floating noise at exact integer boundaries.
453 x, y := math.Ceil(float64(w.Width)*w.Scale-1e-9), math.Ceil(float64(w.Height)*w.Scale-1e-9)
454 if x > math.MaxInt32 || y > math.MaxInt32 {
455 return 0, 0, fmt.Errorf("buffer dimensions overflow")
456 }
457 return int32(x), int32(y), nil
458}
459
460// Present sets both syncobj points on every buffer commit. The caller supplies
461// an already-imported wl_buffer and two persistent Wayland timeline objects.
462func (w *Window) Present(buffer *core.Buffer, acquire, release *drmsyncobj.WpLinuxDrmSyncobjTimeline, acquirePoint, releasePoint uint64) error {
463 if !w.Configured || buffer == nil || acquire == nil || release == nil {
464 return fmt.Errorf("unconfigured surface or missing buffer/timeline")
465 }
466 if !w.FeedbackDone {
467 return fmt.Errorf("dmabuf feedback incomplete")
468 }
469 if !w.FrameReady {
470 return fmt.Errorf("frame callback pending")
471 }
472 pw, ph, err := w.PhysicalSize()
473 if err != nil {
474 return err
475 }
476 if w.Viewport != nil {
477 if err = w.Viewport.SetDestination(w.Width, w.Height); err != nil {
478 return err
479 }
480 } else if w.Scale > 1 {
481 if err = w.Surface.SetBufferScale(int32(w.Scale)); err != nil {
482 return err
483 }
484 }
485 if w.Transparent {
486 if err = w.Surface.SetOpaqueRegion(nil); err != nil {
487 return err
488 }
489 } else {
490 region, e := w.Compositor.CreateRegion()
491 if e != nil {
492 return e
493 }
494 if err = region.Add(0, 0, w.Width, w.Height); err == nil {
495 err = w.Surface.SetOpaqueRegion(region)
496 }
497 _ = region.Destroy()
498 if err != nil {
499 return err
500 }
501 }
502 if err = w.SyncSurface.SetAcquirePoint(acquire, uint32(acquirePoint>>32), uint32(acquirePoint)); err != nil {
503 return err
504 }
505 if err = w.SyncSurface.SetReleasePoint(release, uint32(releasePoint>>32), uint32(releasePoint)); err != nil {
506 return err
507 }
508 if err = w.Surface.Attach(buffer, 0, 0); err != nil {
509 return err
510 }
511 if err = w.Surface.DamageBuffer(0, 0, pw, ph); err != nil {
512 return err
513 }
514 cb, e := w.Surface.Frame()
515 if e != nil {
516 return e
517 }
518 cb.OnDone(func(_ uint32) {
519 if w.readerStarted.Load() {
520 w.post(Event{Kind: FrameEvent})
521 return
522 }
523 w.FrameReady = true
524 })
525 if err = w.Surface.Commit(); err != nil {
526 return err
527 }
528 w.FrameReady, w.Dirty = false, false
529 return nil
530}
531
532// DestroySyncSurface stops associating new explicit-sync points with the surface.
533// The session calls it after committing a null attachment at shutdown.
534func (w *Window) DestroySyncSurface() error {
535 if w == nil || w.SyncSurface == nil {
536 return nil
537 }
538 err := w.SyncSurface.Destroy()
539 if err == nil {
540 w.SyncSurface = nil
541 }
542 return err
543}
544
545// Close destroys children before their parents. The caller first detaches and
546// waits for its own GPU work before destroying its Vulkan allocations.
547func (w *Window) Close() error {
548 if w == nil {
549 return nil
550 }
551 if w.ownsConnection && w.connection != nil && !w.connection.closed {
552 return w.connection.Close()
553 }
554 for _, ev := range w.rawInput {
555 CloseEventFD(ev)
556 }
557 clear(w.rawInput)
558 w.rawInput = nil
559 // The loop sends protocol destructors before Close; the reader is
560 // unblocked by the connection close at the end of this method.
561 if w.inputAdapter != nil {
562 w.inputAdapter.Close()
563 w.inputAdapter = nil
564 }
565 if w.Seat != nil && w.connection == nil {
566 _ = w.releaseSeat()
567 }
568 w.dropCursorDevice()
569 if w.CursorShapes != nil {
570 _ = w.CursorShapes.Destroy()
571 w.CursorShapes = nil
572 }
573 if w.Feedback != nil {
574 _ = w.Feedback.Destroy()
575 w.Feedback = nil
576 }
577 if w.SyncSurface != nil {
578 _ = w.SyncSurface.Destroy()
579 w.SyncSurface = nil
580 }
581 if w.connection != nil && w.Surface != nil {
582 delete(w.connection.windows, w.Surface.ID())
583 w.connection.routes.Delete(w.Surface.ID())
584 }
585 if w.LockSurface != nil {
586 _ = w.LockSurface.Destroy()
587 w.LockSurface = nil
588 }
589 w.closeLayer() // before wl_surface
590 if w.Toplevel != nil {
591 _ = w.Toplevel.Destroy()
592 w.Toplevel = nil
593 }
594 if w.XdgSurface != nil {
595 _ = w.XdgSurface.Destroy()
596 w.XdgSurface = nil
597 }
598 if w.Fractional != nil {
599 _ = w.Fractional.Destroy()
600 w.Fractional = nil
601 }
602 if w.Viewport != nil {
603 _ = w.Viewport.Destroy()
604 w.Viewport = nil
605 }
606 if w.Surface != nil {
607 _ = w.Surface.Destroy()
608 w.Surface = nil
609 }
610 if w.SyncManager != nil {
611 _ = w.SyncManager.Destroy()
612 w.SyncManager = nil
613 }
614 if w.Dmabuf != nil {
615 _ = w.Dmabuf.Destroy()
616 w.Dmabuf = nil
617 }
618 if w.ScaleManager != nil {
619 _ = w.ScaleManager.Destroy()
620 w.ScaleManager = nil
621 }
622 if w.Viewporter != nil {
623 _ = w.Viewporter.Destroy()
624 w.Viewporter = nil
625 }
626 if w.Shell != nil {
627 _ = w.Shell.Destroy()
628 w.Shell = nil
629 }
630 // wl_compositor is bound at most at version 6; its release destructor
631 // needs version 7, so the proxy is only forgotten locally.
632 if w.Compositor != nil {
633 if c := w.Compositor.Context(); c != nil {
634 c.Unregister(w.Compositor)
635 }
636 w.Compositor = nil
637 }
638 if w.connection != nil {
639 w.Display = nil
640 w.Closed = true
641 if w.ownsConnection {
642 return w.connection.Close()
643 }
644 return nil
645 }
646 if w.Display != nil {
647 if w.readerDone != nil {
648 close(w.readerStop)
649 }
650 err := w.Display.Close()
651 if w.readerDone != nil {
652 <-w.readerDone
653 w.readerDone = nil
654 }
655 for len(w.events) > 0 {
656 CloseEventFD(<-w.events)
657 }
658 w.Display = nil
659 return err
660 }
661 return nil
662}
663
664func (w *Window) watchFeedback() {
665 var table []linuxdmabuf.FormatEntry
666 var indices []uint16
667 w.Feedback.OnFormatTable(func(fd *wl.OwnedFD, size uint32) {
668 var err error
669 table, err = linuxdmabuf.ReadFormatTable(fd, size)
670 if err != nil {
671 w.feedbackFailure(err)
672 }
673 })
674 w.Feedback.OnMainDevice(func(device []byte) {
675 if len(device) != 8 {
676 w.feedbackFailure(fmt.Errorf("invalid dmabuf main_device length %d", len(device)))
677 return
678 }
679 if w.readerStarted.Load() {
680 w.post(Event{Kind: FeedbackMainDevice, Device: binary.NativeEndian.Uint64(device)})
681 } else {
682 w.MainDevice = binary.NativeEndian.Uint64(device)
683 }
684 })
685 w.Feedback.OnTrancheFormats(func(data []byte) {
686 var err error
687 indices, err = linuxdmabuf.TrancheIndices(data, len(table))
688 if err != nil {
689 w.feedbackFailure(err)
690 }
691 })
692 w.Feedback.OnTrancheDone(func() {
693 var tranche []linuxdmabuf.FormatEntry
694 for _, i := range indices {
695 tranche = append(tranche, table[i])
696 }
697 if w.readerStarted.Load() {
698 w.post(Event{Kind: FeedbackTranche, Tranche: tranche})
699 } else {
700 w.Tranches = append(w.Tranches, tranche)
701 }
702 indices = nil
703 })
704 w.Feedback.OnDone(func() {
705 if w.readerStarted.Load() {
706 w.post(Event{Kind: FeedbackFormats, Formats: table})
707 w.post(Event{Kind: FeedbackComplete})
708 } else {
709 w.Formats = table
710 w.FeedbackDone = true
711 }
712 })
713}
714
715func (w *Window) feedbackFailure(err error) {
716 if w.readerStarted.Load() {
717 w.post(Event{Kind: FeedbackError, Err: err})
718 } else {
719 w.feedbackErr = err
720 }
721}