1package server
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "os"
8 "runtime"
9 "strings"
10 "sync"
11 "sync/atomic"
12 "unsafe"
13
14 "github.com/bnema/purego"
15 "golang.org/x/sys/unix"
16)
17
18// Handler receives every request sent to a resource. args borrows libwayland's
19// argument array and must not be retained after the handler returns.
20type Handler func(r *Resource, opcode uint32, args []Arg)
21
22// BindFunc runs when a client binds a global.
23type BindFunc func(c Client, version, id uint32)
24
25// Client is a connected wl_client. It is a comparable handle to the C object,
26// usable as a map key until the client is destroyed (see Client.OnDestroy).
27type Client struct{ c uintptr }
28
29// Resource is a server-side wl_resource.
30type Resource struct {
31 c uintptr
32 id uint32
33 version int32
34 client uintptr
35 iface *Interface
36 handler Handler
37 OnDestroy func()
38 gone bool
39 bare bool // created by libwayland, no destroy callback
40
41 // Data holds the owner's state, typically the generated wrapper.
42 Data any
43}
44
45// live holds Go state reachable from C callbacks. It is only touched on the
46// display goroutine, so it needs no lock.
47var live = struct {
48 resources map[uintptr]*Resource
49 binds map[uintptr]BindFunc
50 nextBind uintptr
51 // clientGone maps wl_listener addresses to OnDestroy functions.
52 clientGone map[uintptr]func()
53 // fds maps AddFD/AddFDRW tokens to their sources.
54 fds map[uintptr]*Source
55 nextFD uintptr
56 globals map[uintptr]*Global
57 displays map[uintptr]*Display
58}{resources: map[uintptr]*Resource{}, binds: map[uintptr]BindFunc{}, clientGone: map[uintptr]func(){}, fds: map[uintptr]*Source{}, globals: map[uintptr]*Global{}, displays: map[uintptr]*Display{}}
59
60// Display owns a wl_display. All methods except Do and Post must run on the
61// goroutine that calls Run, or before Run starts.
62//
63// Resource, bind and client-listener state is shared by the whole process, so
64// only one Display may be running at a time per process.
65//
66// Do and Post wake the loop through an eventfd so calls run at once instead of
67// after the dispatch timeout.
68type Display struct {
69 c uintptr
70 loop uintptr
71 calls chan func()
72 wake int
73 // wakeSrc owns libwayland's duplicate of wake; only removing the source
74 // closes it, destroying the display does not.
75 wakeSrc uintptr
76 stopped chan struct{}
77 state atomic.Uint32
78
79 // postMu guards posts and closed. spare is only used by the display
80 // goroutine: it is the emptied batch swapped back in on the next drain, so
81 // steady-state Post does not allocate.
82 postMu sync.Mutex
83 posts []func()
84 spare []func()
85 // armed is true while an eventfd wake for posts is pending, so a burst of
86 // Posts writes the eventfd once.
87 armed atomic.Bool
88 closed atomic.Bool // set under postMu before the wake fd is closed
89 // wakeWrites counts eventfd writes made for posts (diagnostics and tests).
90 wakeWrites atomic.Uint64
91
92 // sources are the AddFD sources not yet removed (display goroutine only).
93 sources map[*Source]struct{}
94 filter func(Client, *Global) bool
95 pendingGlobal *Global
96}
97
98// maxDrainRounds bounds how many times one loop iteration re-drains calls and
99// posts that were queued by callbacks, so a self-posting callback cannot starve
100// libwayland's dispatch and client flushing.
101const maxDrainRounds = 8
102
103func NewDisplay() (*Display, error) {
104 if err := load(); err != nil {
105 return nil, err
106 }
107 c := wlDisplayCreate()
108 if c == 0 {
109 return nil, errors.New("purego-libwayland: wl_display_create failed")
110 }
111 wake, err := unix.Eventfd(0, unix.EFD_CLOEXEC|unix.EFD_NONBLOCK)
112 if err != nil {
113 wlDisplayDestroy(c)
114 return nil, fmt.Errorf("purego-libwayland: eventfd: %w", err)
115 }
116 d := &Display{c: c, loop: wlDisplayGetEventLoop(c), calls: make(chan func(), 64), wake: wake, stopped: make(chan struct{}), sources: map[*Source]struct{}{}}
117 // The callback only drains the counter; Run then executes queued calls.
118 if d.wakeSrc = wlEventLoopAddFD(d.loop, int32(wake), 1 /* WL_EVENT_READABLE */, cbWake, 0); d.wakeSrc == 0 {
119 unix.Close(wake)
120 wlDisplayDestroy(c)
121 return nil, errors.New("purego-libwayland: wl_event_loop_add_fd failed")
122 }
123 live.displays[c] = d
124 return d, nil
125}
126
127// AddSocketFD serves clients on an already bound, listening unix socket.
128func (d *Display) AddSocketFD(fd int) error {
129 if wlDisplayAddSocketFD(d.c, int32(fd)) != 0 {
130 return fmt.Errorf("purego-libwayland: wl_display_add_socket_fd(%d) failed", fd)
131 }
132 return nil
133}
134
135func (d *Display) CreateGlobal(iface *Interface, version int32, bindFn BindFunc) error {
136 _, err := d.AddGlobal(iface, version, bindFn)
137 return err
138}
139
140// Global is a global that can be withdrawn, such as a wl_output on hotplug.
141type Global struct {
142 c uintptr
143 d *Display
144 interfaceName string
145 version int32
146}
147
148// Interface returns the advertised interface name without allocating. It is
149// available even in filter calls made synchronously during AddGlobal.
150func (g *Global) Interface() string { return g.interfaceName }
151
152// Version returns the advertised version, including during AddGlobal filtering.
153func (g *Global) Version() int32 { return g.version }
154
155// SetGlobalFilter controls global visibility and binding for each client. nil
156// removes the filter. Install it before advertising restricted globals; it does
157// not retract announcements already sent. The callback must not block or create
158// globals. Display goroutine only.
159func (d *Display) SetGlobalFilter(fn func(Client, *Global) bool) {
160 d.filter = fn
161 callback := uintptr(0)
162 if fn != nil {
163 callback = cbGlobalFilter
164 }
165 wlDisplaySetGlobalFilter(d.c, callback, d.c)
166}
167
168func globalFilter(client, global, data uintptr) uint32 {
169 d := live.displays[data]
170 if d == nil || d.filter == nil {
171 return 1
172 }
173 g := live.globals[global]
174 // wl_global_create announces synchronously, before returning its pointer.
175 if g == nil {
176 g = d.pendingGlobal
177 }
178 if g != nil && d.filter(Client{client}, g) {
179 return 1
180 }
181 return 0
182}
183
184// AddGlobal advertises a global and returns a handle to remove it later.
185func (d *Display) AddGlobal(iface *Interface, version int32, bindFn BindFunc) (*Global, error) {
186 live.nextBind++
187 token := live.nextBind
188 live.binds[token] = bindFn
189 g := &Global{d: d, interfaceName: iface.Name, version: version}
190 d.pendingGlobal = g
191 c := wlGlobalCreate(d.c, iface.c, version, token, cbBind)
192 d.pendingGlobal = nil
193 if c == 0 {
194 delete(live.binds, token)
195 return nil, fmt.Errorf("purego-libwayland: wl_global_create(%s) failed", iface.Name)
196 }
197 g.c = c
198 live.globals[c] = g
199 return g, nil
200}
201
202// Remove withdraws the global: clients get wl_registry.global_remove.
203// Existing resources stay valid. A client that binds before it sees the
204// removal still gets its bind function called, so it always receives a
205// resource (the protocol allows binding a just-removed global); the bind
206// function should then send inert state. The global is freed with the
207// display. Display goroutine only; idempotent.
208func (g *Global) Remove() {
209 if g == nil || g.c == 0 {
210 return
211 }
212 wlGlobalRemove(g.c)
213 g.c = 0
214}
215
216// AckGlobalRemove implements wl_fixes.ack_global_remove: libwayland checks
217// that registry announced the removed global name, posting
218// wl_fixes.invalid_ack_remove on fixes otherwise. It reports false when
219// libwayland-server is older than 1.26 and has no support.
220// Display goroutine only.
221func AckGlobalRemove(fixes, registry *Resource, name uint32) bool {
222 if symFixesAckGlobalRemove == 0 {
223 return false
224 }
225 if fixes == nil || fixes.gone || registry == nil || registry.gone {
226 return true
227 }
228 wlFixesHandleAckGlobalRemove(fixes.c, registry.c, name)
229 return true
230}
231
232// Close destroys a display that has not started Run. It is a no-op once Run
233// has started; Run owns destruction in that case. Call before Run starts.
234func (d *Display) Close() {
235 if !d.state.CompareAndSwap(0, 1) {
236 return
237 }
238 d.destroy()
239 close(d.stopped)
240}
241
242// destroy frees the display and the wake-up fds.
243func (d *Display) destroy() {
244 // Stop accepting posts before the wake fd is closed (its number could be
245 // reused), and drop what never ran.
246 d.postMu.Lock()
247 d.closed.Store(true)
248 clear(d.posts)
249 d.posts, d.spare = nil, nil
250 d.postMu.Unlock()
251 for s := range d.sources {
252 s.remove()
253 }
254 clear(d.sources)
255 wlDisplayDestroyClients(d.c)
256 wlEventSourceRemove(d.wakeSrc)
257 wlDisplayDestroy(d.c)
258 delete(live.displays, d.c)
259 for p, g := range live.globals {
260 if g.d == d {
261 g.c = 0
262 delete(live.globals, p)
263 }
264 }
265 d.filter = nil
266 unix.Close(d.wake)
267}
268
269// Run dispatches libwayland on one OS-locked goroutine until ctx is done.
270func (d *Display) Run(ctx context.Context) error {
271 if !d.state.CompareAndSwap(0, 2) {
272 return errors.New("purego-libwayland: display already started or closed")
273 }
274 runtime.LockOSThread()
275 defer runtime.UnlockOSThread()
276 defer close(d.stopped)
277 defer d.destroy()
278 // Wait for the event loop's epoll fd in Go's netpoller instead of blocking
279 // in epoll_wait inside C: the goroutine parks, its P is released, and the
280 // runtime's sysmon can sleep deeply while the server is idle. libwayland
281 // is then dispatched without blocking.
282 loopFile, err := d.openLoopFile()
283 if err != nil {
284 return err
285 }
286 defer loopFile.Close() // runs before destroy: the dup must not outlive the epoll fd
287 rc, err := loopFile.SyscallConn()
288 if err != nil {
289 return fmt.Errorf("purego-libwayland: event loop fd: %w", err)
290 }
291 // Cancellation wakes the loop through the eventfd.
292 stop := context.AfterFunc(ctx, d.wakeGuarded)
293 defer stop()
294 ready := func(fd uintptr) bool {
295 // Level-triggered check: the netpoller is edge-triggered, and a
296 // dispatch can leave events (libwayland takes 32 per epoll_wait).
297 fds := [1]unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}}
298 n, err := unix.Poll(fds[:], 0)
299 return n > 0 || (err != nil && err != unix.EINTR)
300 }
301 consecutiveFailures := 0
302 for {
303 if ctx.Err() != nil {
304 return nil
305 }
306 // Run every queued post and call. Wakes are coalesced, so the eventfd
307 // is not written once per item; it only makes the wait below
308 // return at once when work arrives while the loop is asleep.
309 d.drain()
310 wlDisplayFlushClients(d.c)
311 if err := rc.Read(ready); err != nil {
312 return fmt.Errorf("purego-libwayland: wait for event loop: %w", err)
313 }
314 if ctx.Err() != nil {
315 return nil
316 }
317 if wlEventLoopDispatch(d.loop, 0) < 0 {
318 errno := lastErrno()
319 consecutiveFailures++
320 if consecutiveFailures < 16 {
321 continue
322 }
323 return fmt.Errorf("purego-libwayland: wl_event_loop_dispatch failed %d times in a row: %w", consecutiveFailures, errno)
324 }
325 consecutiveFailures = 0
326 wlDisplayFlushClients(d.c)
327 }
328}
329
330// openLoopFile wraps a duplicate of the event loop's epoll fd for the netpoller.
331func (d *Display) openLoopFile() (*os.File, error) {
332 fd := int(wlEventLoopGetFD(d.loop))
333 if fd < 0 {
334 return nil, errors.New("purego-libwayland: wl_event_loop_get_fd failed")
335 }
336 dup, err := unix.FcntlInt(uintptr(fd), unix.F_DUPFD_CLOEXEC, 0)
337 if err != nil {
338 return nil, fmt.Errorf("purego-libwayland: dup event loop fd: %w", err)
339 }
340 if err := unix.SetNonblock(dup, true); err != nil {
341 unix.Close(dup)
342 return nil, fmt.Errorf("purego-libwayland: nonblock event loop fd: %w", err)
343 }
344 return os.NewFile(uintptr(dup), "wl-event-loop"), nil
345}
346
347// drain runs queued posts and Do calls until none are left, at most
348// maxDrainRounds rounds. Posts are taken again after each call is received and
349// before it runs, so a Post that happened before a Do always runs before that
350// Do's function. When the cap is hit the loop is woken again so the next
351// dispatch returns at once.
352func (d *Display) drain() {
353 for range maxDrainRounds {
354 ran := d.runPosts()
355 calls:
356 for {
357 select {
358 case fn := <-d.calls:
359 d.runPosts()
360 fn()
361 ran = true
362 default:
363 break calls
364 }
365 }
366 if !ran {
367 return
368 }
369 }
370 d.armed.Store(true)
371 d.wakeLoop()
372}
373
374// runPosts runs the posts queued so far and reports whether there were any.
375func (d *Display) runPosts() bool {
376 // Clear armed before taking the batch: a Post that lands after the swap
377 // sees armed false and writes the eventfd again.
378 d.armed.Store(false)
379 d.postMu.Lock()
380 batch := d.posts
381 d.posts, d.spare = d.spare, nil
382 d.postMu.Unlock()
383 for _, fn := range batch {
384 fn()
385 }
386 clear(batch)
387 d.spare = batch[:0]
388 return len(batch) > 0
389}
390
391// wakeOne is the eventfd increment. It is read-only, so sharing it between
392// goroutines is safe and avoids a per-call buffer (which escapes under -race).
393var wakeOne = [8]byte{1}
394
395// wakeLoop writes the eventfd on behalf of posts. The caller must hold postMu
396// (or be the display goroutine, which destroy cannot overlap) so the fd cannot
397// be closed, and its number reused, underneath the write.
398func (d *Display) wakeLoop() {
399 d.wakeWrites.Add(1)
400 _, _ = unix.Write(d.wake, wakeOne[:])
401}
402
403// wakeGuarded wakes the loop from any goroutine: it takes postMu and skips the
404// write once the display is closed, so a late caller never writes to a reused
405// fd number.
406func (d *Display) wakeGuarded() {
407 d.postMu.Lock()
408 defer d.postMu.Unlock()
409 if d.closed.Load() {
410 return
411 }
412 _, _ = unix.Write(d.wake, wakeOne[:])
413}
414
415// Post queues fn to run on the display goroutine and returns at once. Unlike
416// Do it never waits, and unlike Do it may be called from any goroutine,
417// including the display goroutine itself.
418//
419// Posts run in the order they were made, and a Post made before a Do (by the
420// same goroutine, or otherwise known to happen before it) runs before that Do's
421// function. A Post made by a posted function or a Do function runs in a later
422// drain round of the same loop iteration (at most 8 rounds; past that the loop
423// is woken again and continues after one libwayland dispatch). Wakes are coalesced:
424// any number of Posts while one wake is pending write the eventfd once.
425//
426// fn must not block: it runs on the goroutine that serves every client. After
427// the display has stopped or been closed, Post silently drops fn, and posts
428// that had not run when the display stopped are dropped too. In steady state
429// Post does not allocate.
430//
431// The queue is unbounded: Post applies no backpressure, so a producer that
432// outpaces the display goroutine grows memory without limit. Callers that can
433// produce faster than the loop runs must bound or coalesce their own posts.
434func (d *Display) Post(fn func()) {
435 if fn == nil {
436 return
437 }
438 d.postMu.Lock()
439 defer d.postMu.Unlock()
440 if d.closed.Load() {
441 return
442 }
443 d.posts = append(d.posts, fn)
444 // Written under postMu so destroy cannot close the fd underneath.
445 if d.armed.CompareAndSwap(false, true) {
446 d.wakeLoop()
447 }
448}
449
450// Source is an fd registered with the display's event loop by AddFD.
451type Source struct {
452 d *Display
453 c uintptr
454 token uintptr
455 onReadable, onWritable func()
456}
457
458// AddFD calls onReadable on the display goroutine whenever fd is readable. The
459// registration is level-triggered: onReadable must consume the readiness (read
460// the eventfd or timerfd, accept, ...) or it is called again at once. It
461// should not block.
462//
463// onReadable is also called when the fd hangs up or errors (EPOLLHUP,
464// EPOLLERR), which level-triggered polling keeps reporting. The callback should
465// read the fd, and on EOF or error call Remove (it may do so from within the
466// callback), or it is called again at once.
467//
468// libwayland duplicates fd; the caller still owns and must close its own
469// descriptor, and should do so after Remove. Call AddFD only on the display
470// goroutine or before Run starts. Sources not removed are released when the
471// display is destroyed.
472func (d *Display) AddFD(fd int, onReadable func()) (*Source, error) {
473 return d.AddFDRW(fd, onReadable, nil)
474}
475
476// AddFDRW is AddFD with an optional writable callback. Writable interest starts
477// disabled; use SetWritable to arm it after a non-blocking write returns EAGAIN.
478// Both callbacks must not block. Readable runs first; removing the source there
479// suppresses writable delivery in the same dispatch.
480func (d *Display) AddFDRW(fd int, onReadable, onWritable func()) (*Source, error) {
481 if fd < 0 {
482 return nil, fmt.Errorf("purego-libwayland: AddFD: invalid fd %d", fd)
483 }
484 if onReadable == nil {
485 return nil, errors.New("purego-libwayland: AddFD: nil callback")
486 }
487 if d.closed.Load() {
488 return nil, errors.New("purego-libwayland: AddFD: display closed")
489 }
490 live.nextFD++
491 token := live.nextFD
492 s := &Source{d: d, token: token, onReadable: onReadable, onWritable: onWritable}
493 live.fds[token] = s
494 c := wlEventLoopAddFD(d.loop, int32(fd), 1 /* WL_EVENT_READABLE */, cbFD, token)
495 if c == 0 {
496 delete(live.fds, token)
497 return nil, fmt.Errorf("purego-libwayland: wl_event_loop_add_fd(%d) failed", fd)
498 }
499 s.c = c
500 d.sources[s] = struct{}{}
501 return s, nil
502}
503
504// SetWritable toggles writable interest while always retaining readable interest.
505// Disable it when buffered writes are drained to avoid a busy loop. Display
506// goroutine only. A removed source is a no-op; enabling requires onWritable.
507func (s *Source) SetWritable(on bool) error {
508 if s == nil || s.c == 0 {
509 return nil
510 }
511 mask := uint32(1)
512 if on {
513 if s.onWritable == nil {
514 return errors.New("purego-libwayland: no writable callback")
515 }
516 mask |= 2
517 }
518 if wlEventSourceFDUpdate(s.c, mask) < 0 {
519 return lastErrno()
520 }
521 return nil
522}
523
524// Remove stops the callbacks and releases libwayland's duplicate of the fd.
525// It may be called from the source's own callback. Display goroutine only;
526// idempotent, and a no-op after the display was destroyed.
527func (s *Source) Remove() {
528 if s == nil || s.c == 0 {
529 return
530 }
531 delete(s.d.sources, s)
532 s.remove()
533}
534
535func (s *Source) remove() {
536 delete(live.fds, s.token)
537 wlEventSourceRemove(s.c)
538 s.c = 0
539}
540
541// Do runs fn on the display goroutine and waits for it. It returns false if
542// the display loop has stopped (fn was not run, or its run was not confirmed).
543// Do must not be called from the display goroutine: it would deadlock.
544func (d *Display) Do(fn func()) bool {
545 done := make(chan struct{})
546 select {
547 case d.calls <- func() { fn(); close(done) }:
548 case <-d.stopped:
549 return false
550 }
551 d.wakeGuarded()
552 select {
553 case <-done:
554 return true
555 case <-d.stopped:
556 // If both are ready, prefer confirmation of a completed call.
557 select {
558 case <-done:
559 return true
560 default:
561 return false
562 }
563 }
564}
565
566// Stopped is closed after Run returns and the display is destroyed.
567func (d *Display) Stopped() <-chan struct{} { return d.stopped }
568
569// LiveResources reports resources not yet destroyed. Display goroutine only.
570func LiveResources() int { return len(live.resources) }
571
572// Credentials are the peer credentials of a client, read from the socket
573// (SO_PEERCRED) when it connected. UID/GID are effective IDs in the server's
574// namespaces; an unmappable ID is the namespace overflow ID. PID may be zero
575// even for a valid client when its PID namespace is not visible to the server.
576type Credentials struct {
577 PID int
578 UID uint32
579 GID uint32
580}
581
582var credScratch [3]int32
583
584var errInvalidClient = errors.New("purego-libwayland: invalid client")
585
586// Credentials returns the peer credentials of the client. It returns an error
587// for an invalid (zero) Client. It does not allocate. Display goroutine only.
588func (c Client) Credentials() (Credentials, error) {
589 if c.c == 0 {
590 return Credentials{}, errInvalidClient
591 }
592 // Package-level scratch (display goroutine only) avoids heap-escaping locals.
593 purego.Syscall6(symClientGetCredentials, c.c, uintptr(unsafe.Pointer(&credScratch[0])), uintptr(unsafe.Pointer(&credScratch[1])), uintptr(unsafe.Pointer(&credScratch[2])), 0, 0)
594 return Credentials{PID: int(credScratch[0]), UID: uint32(credScratch[1]), GID: uint32(credScratch[2])}, nil
595}
596
597// PID is the process ID of the client, from Credentials. It is zero for an
598// invalid client or a peer outside the server's visible PID namespaces.
599// Display goroutine only.
600func (c Client) PID() int {
601 cr, err := c.Credentials()
602 if err != nil {
603 return 0
604 }
605 return cr.PID
606}
607
608func (c Client) CreateResource(iface *Interface, version int32, id uint32, h Handler) (*Resource, error) {
609 rc := wlResourceCreate(c.c, iface.c, version, id)
610 if rc == 0 {
611 return nil, fmt.Errorf("purego-libwayland: wl_resource_create(%s) failed", iface.Name)
612 }
613 // id 0 asks libwayland to allocate a server-side ID.
614 if id == 0 {
615 id = wlResourceGetID(rc)
616 }
617 r := &Resource{c: rc, id: id, version: version, client: c.c, iface: iface, handler: h}
618 live.resources[rc] = r
619 wlResourceSetDispatcher(rc, cbDispatcher, implMarker(), 0, cbDestroy)
620 return r, nil
621}
622
623func (r *Resource) Alive() bool { return !r.gone }
624func (r *Resource) ID() uint32 {
625 if r.gone {
626 return 0
627 }
628 return r.id
629}
630func (r *Resource) Version() int32 {
631 if r.gone {
632 return 0
633 }
634 return r.version
635}
636func (r *Resource) Client() Client {
637 if r.gone {
638 return Client{}
639 }
640 return Client{r.client}
641}
642func (r *Resource) Iface() *Interface { return r.iface }
643
644// Destroy frees the resource. It is a no-op once the resource is gone, so
645// generated destructor requests and handlers may both call it.
646func (r *Resource) Destroy() {
647 if !r.gone {
648 wlResourceDestroy(r.c)
649 // A bare handle gets no destroy callback to mark it gone.
650 r.gone = r.gone || r.bare
651 }
652}
653
654// eventArgs holds the wl_argument array of the event being posted.
655// libwayland copies it before wl_resource_post_event_array returns.
656// Display goroutine only; post_event_array does not call back into Go, so
657// PostEvent is not re-entrant. No protocol event has more than 8 arguments.
658var eventArgs [16]Arg
659
660// PostEvent sends an event. Arguments must match the event signature; build
661// string and array arguments with a Pinner and unpin it after this returns.
662// Calling it after destruction is safe and does nothing.
663func (r *Resource) PostEvent(opcode uint32, args ...Arg) {
664 if r.gone {
665 return
666 }
667 if len(args) > len(eventArgs) {
668 panic("purego-libwayland: too many event arguments")
669 }
670 var p unsafe.Pointer
671 if len(args) > 0 {
672 copy(eventArgs[:], args)
673 p = unsafe.Pointer(&eventArgs[0])
674 }
675 wlResourcePostEventArr(r.c, opcode, p)
676}
677
678// PostError sends a protocol error and disconnects the client.
679// Calling it after destruction is safe and does nothing.
680func (r *Resource) PostError(code uint32, msg string) {
681 if r.gone {
682 return
683 }
684 // wl_resource_post_error takes a printf format; escape it and pass no
685 // variadic arguments (purego clears AL, as SysV varargs require).
686 b := append([]byte(strings.ReplaceAll(msg, "%", "%%")), 0)
687 var pin runtime.Pinner
688 pin.Pin(&b[0])
689 defer pin.Unpin()
690 wlResourcePostError(r.c, code, uintptr(unsafe.Pointer(&b[0])))
691 runtime.KeepAlive(b)
692}
693
694// implMarker is a non-nil implementation pointer. libwayland only passes it
695// back to our dispatcher.
696func implMarker() uintptr {
697 if marker == 0 {
698 tablesMu.Lock()
699 if err := tables.reserve(8); err != nil {
700 panic(err)
701 }
702 _, marker = tables.alloc(8)
703 tablesMu.Unlock()
704 }
705 return marker
706}
707
708var marker uintptr
709
710// --- C callbacks (display goroutine) ---
711
712func dispatch(_ uintptr, target uintptr, opcode uint32, _ unsafe.Pointer, args unsafe.Pointer) int32 {
713 r := live.resources[target]
714 if r == nil || opcode >= uint32(len(r.iface.Requests)) {
715 return 0
716 }
717 m := &r.iface.Requests[opcode]
718 if r.handler == nil {
719 closeRequestFDs(m.fdPositions, args)
720 return 0
721 }
722 var a []Arg
723 if m.argc > 0 {
724 a = unsafe.Slice((*Arg)(args), m.argc)
725 }
726 r.handler(r, opcode, a)
727 return 0
728}
729
730func destroyed(res uintptr) {
731 r := live.resources[res]
732 delete(live.resources, res)
733 if r == nil {
734 return
735 }
736 r.gone = true
737 if r.OnDestroy != nil {
738 r.OnDestroy()
739 }
740}
741
742// wake drains a Display's eventfd (wl_event_loop_fd_func_t).
743func wake(fd int32, mask uint32, data uintptr) int32 {
744 var buf [8]byte
745 _, _ = unix.Read(int(fd), buf[:])
746 return 0
747}
748
749// fdReadable dispatches an AddFD source (wl_event_loop_fd_func_t).
750func fdReadable(_ int32, mask uint32, data uintptr) int32 {
751 if s := live.fds[data]; s != nil {
752 if mask&(1|4|8) != 0 {
753 s.onReadable()
754 }
755 if s.c != 0 && mask&2 != 0 && s.onWritable != nil {
756 s.onWritable()
757 }
758 }
759 return 0
760}
761
762func bind(client uintptr, data uintptr, version uint32, id uint32) {
763 if fn := live.binds[data]; fn != nil {
764 fn(Client{client}, version, id)
765 }
766}
767
768// closeRequestFDs takes ownership of fd arguments when there is no handler.
769func closeRequestFDs(positions []int, args unsafe.Pointer) {
770 for _, i := range positions {
771 _ = unix.Close((*Arg)(unsafe.Add(args, i*8)).Fd())
772 }
773}
774
775// argCount counts arguments in a libwayland signature ("?oii", "2n", ...).
776func argCount(sig string) int {
777 n := 0
778 for _, ch := range sig {
779 if ch != '?' && (ch < '0' || ch > '9') {
780 n++
781 }
782 }
783 return n
784}