1package server
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "runtime"
8 "strings"
9 "sync/atomic"
10 "unsafe"
11
12 "golang.org/x/sys/unix"
13)
14
15// Handler receives every request sent to a resource.
16type Handler func(r *Resource, opcode uint32, args []Arg)
17
18// BindFunc runs when a client binds a global.
19type BindFunc func(c Client, version, id uint32)
20
21// Client is a connected wl_client.
22type Client struct{ c uintptr }
23
24// Resource is a server-side wl_resource.
25type Resource struct {
26 c uintptr
27 iface *Interface
28 handler Handler
29 OnDestroy func()
30 gone bool
31
32 // Data holds the owner's state, typically the generated wrapper.
33 Data any
34}
35
36// live holds Go state reachable from C callbacks. It is only touched on the
37// display goroutine, so it needs no lock.
38var live = struct {
39 resources map[uintptr]*Resource
40 binds map[uintptr]BindFunc
41 nextBind uintptr
42}{resources: map[uintptr]*Resource{}, binds: map[uintptr]BindFunc{}}
43
44// Display owns a wl_display. All methods except Do must run on the goroutine
45// that calls Run, or before Run starts.
46// Do wakes the loop through an eventfd so calls run at once instead of after
47// the dispatch timeout.
48type Display struct {
49 c uintptr
50 loop uintptr
51 calls chan func()
52 wake int
53 // wakeSrc owns libwayland's duplicate of wake; only removing the source
54 // closes it, destroying the display does not.
55 wakeSrc uintptr
56 stopped chan struct{}
57 state atomic.Uint32
58}
59
60func NewDisplay() (*Display, error) {
61 if err := load(); err != nil {
62 return nil, err
63 }
64 c := wlDisplayCreate()
65 if c == 0 {
66 return nil, errors.New("purego-libwayland: wl_display_create failed")
67 }
68 wake, err := unix.Eventfd(0, unix.EFD_CLOEXEC|unix.EFD_NONBLOCK)
69 if err != nil {
70 wlDisplayDestroy(c)
71 return nil, fmt.Errorf("purego-libwayland: eventfd: %w", err)
72 }
73 d := &Display{c: c, loop: wlDisplayGetEventLoop(c), calls: make(chan func(), 64), wake: wake, stopped: make(chan struct{})}
74 // The callback only drains the counter; Run then executes queued calls.
75 if d.wakeSrc = wlEventLoopAddFD(d.loop, int32(wake), 1 /* WL_EVENT_READABLE */, cbWake, 0); d.wakeSrc == 0 {
76 unix.Close(wake)
77 wlDisplayDestroy(c)
78 return nil, errors.New("purego-libwayland: wl_event_loop_add_fd failed")
79 }
80 return d, nil
81}
82
83// AddSocketFD serves clients on an already bound, listening unix socket.
84func (d *Display) AddSocketFD(fd int) error {
85 if wlDisplayAddSocketFD(d.c, int32(fd)) != 0 {
86 return fmt.Errorf("purego-libwayland: wl_display_add_socket_fd(%d) failed", fd)
87 }
88 return nil
89}
90
91func (d *Display) CreateGlobal(iface *Interface, version int32, bindFn BindFunc) error {
92 _, err := d.AddGlobal(iface, version, bindFn)
93 return err
94}
95
96// Global is a global that can be withdrawn, such as a wl_output on hotplug.
97type Global struct{ c uintptr }
98
99// AddGlobal advertises a global and returns a handle to remove it later.
100func (d *Display) AddGlobal(iface *Interface, version int32, bindFn BindFunc) (*Global, error) {
101 live.nextBind++
102 token := live.nextBind
103 live.binds[token] = bindFn
104 c := wlGlobalCreate(d.c, iface.c, version, token, cbBind)
105 if c == 0 {
106 delete(live.binds, token)
107 return nil, fmt.Errorf("purego-libwayland: wl_global_create(%s) failed", iface.Name)
108 }
109 return &Global{c: c}, nil
110}
111
112// Remove withdraws the global: clients get wl_registry.global_remove.
113// Existing resources stay valid. A client that binds before it sees the
114// removal still gets its bind function called, so it always receives a
115// resource (the protocol allows binding a just-removed global); the bind
116// function should then send inert state. The global is freed with the
117// display. Display goroutine only; idempotent.
118func (g *Global) Remove() {
119 if g == nil || g.c == 0 {
120 return
121 }
122 wlGlobalRemove(g.c)
123 g.c = 0
124}
125
126// Close destroys a display that has not started Run. It is a no-op once Run
127// has started; Run owns destruction in that case. Call before Run starts.
128func (d *Display) Close() {
129 if !d.state.CompareAndSwap(0, 1) {
130 return
131 }
132 d.destroy()
133 close(d.stopped)
134}
135
136// destroy frees the display and the wake-up fds.
137func (d *Display) destroy() {
138 wlDisplayDestroyClients(d.c)
139 wlEventSourceRemove(d.wakeSrc)
140 wlDisplayDestroy(d.c)
141 unix.Close(d.wake)
142}
143
144// Run dispatches libwayland on one OS-locked goroutine until ctx is done.
145func (d *Display) Run(ctx context.Context) error {
146 if !d.state.CompareAndSwap(0, 2) {
147 return errors.New("purego-libwayland: display already started or closed")
148 }
149 runtime.LockOSThread()
150 defer runtime.UnlockOSThread()
151 defer close(d.stopped)
152 defer d.destroy()
153 for {
154 if ctx.Err() != nil {
155 return nil
156 }
157 // Run every queued call; each Do also wrote the eventfd.
158 drain:
159 for {
160 select {
161 case fn := <-d.calls:
162 fn()
163 default:
164 break drain
165 }
166 }
167 wlDisplayFlushClients(d.c)
168 // The timeout only bounds how late ctx cancellation is seen.
169 if wlEventLoopDispatch(d.loop, 100) < 0 {
170 return errors.New("purego-libwayland: wl_event_loop_dispatch failed")
171 }
172 wlDisplayFlushClients(d.c)
173 }
174}
175
176// Do runs fn on the display goroutine and waits for it. It returns false if
177// the display loop has stopped (fn was not run, or its run was not confirmed).
178// Do must not be called from the display goroutine: it would deadlock.
179func (d *Display) Do(fn func()) bool {
180 done := make(chan struct{})
181 select {
182 case d.calls <- func() { fn(); close(done) }:
183 case <-d.stopped:
184 return false
185 }
186 var one = [8]byte{1}
187 _, _ = unix.Write(d.wake, one[:])
188 select {
189 case <-done:
190 return true
191 case <-d.stopped:
192 // If both are ready, prefer confirmation of a completed call.
193 select {
194 case <-done:
195 return true
196 default:
197 return false
198 }
199 }
200}
201
202// Stopped is closed after Run returns and the display is destroyed.
203func (d *Display) Stopped() <-chan struct{} { return d.stopped }
204
205// LiveResources reports resources not yet destroyed. Display goroutine only.
206func LiveResources() int { return len(live.resources) }
207
208// PID is the process ID of the client, read from the socket credentials
209// (SO_PEERCRED) when it connected. Zero for an invalid client.
210// Display goroutine only.
211func (c Client) PID() int {
212 if c.c == 0 {
213 return 0
214 }
215 var pid, uid, gid int32
216 wlClientGetCredentials(c.c, unsafe.Pointer(&pid), unsafe.Pointer(&uid), unsafe.Pointer(&gid))
217 return int(pid)
218}
219
220func (c Client) CreateResource(iface *Interface, version int32, id uint32, h Handler) (*Resource, error) {
221 rc := wlResourceCreate(c.c, iface.c, version, id)
222 if rc == 0 {
223 return nil, fmt.Errorf("purego-libwayland: wl_resource_create(%s) failed", iface.Name)
224 }
225 r := &Resource{c: rc, iface: iface, handler: h}
226 live.resources[rc] = r
227 wlResourceSetDispatcher(rc, cbDispatcher, implMarker(), 0, cbDestroy)
228 return r, nil
229}
230
231func (r *Resource) Alive() bool { return !r.gone }
232func (r *Resource) ID() uint32 {
233 if r.gone {
234 return 0
235 }
236 return wlResourceGetID(r.c)
237}
238func (r *Resource) Version() int32 {
239 if r.gone {
240 return 0
241 }
242 return wlResourceGetVersion(r.c)
243}
244func (r *Resource) Client() Client {
245 if r.gone {
246 return Client{}
247 }
248 return Client{wlResourceGetClient(r.c)}
249}
250func (r *Resource) Iface() *Interface { return r.iface }
251
252// Destroy frees the resource. It is a no-op once the resource is gone, so
253// generated destructor requests and handlers may both call it.
254func (r *Resource) Destroy() {
255 if !r.gone {
256 wlResourceDestroy(r.c)
257 }
258}
259
260// PostEvent sends an event. Arguments must match the event signature; build
261// string and array arguments with a Pinner and unpin it after this returns.
262// Calling it after destruction is safe and does nothing.
263func (r *Resource) PostEvent(opcode uint32, args ...Arg) {
264 if r.gone {
265 return
266 }
267 var p unsafe.Pointer
268 if len(args) > 0 {
269 p = unsafe.Pointer(&args[0])
270 }
271 var pin runtime.Pinner
272 if p != nil {
273 pin.Pin(p)
274 defer pin.Unpin()
275 }
276 wlResourcePostEventArr(r.c, opcode, p)
277 runtime.KeepAlive(args)
278}
279
280// PostError sends a protocol error and disconnects the client.
281// Calling it after destruction is safe and does nothing.
282func (r *Resource) PostError(code uint32, msg string) {
283 if r.gone {
284 return
285 }
286 // wl_resource_post_error takes a printf format; escape it and pass no
287 // variadic arguments (purego clears AL, as SysV varargs require).
288 b := append([]byte(strings.ReplaceAll(msg, "%", "%%")), 0)
289 var pin runtime.Pinner
290 pin.Pin(&b[0])
291 defer pin.Unpin()
292 wlResourcePostError(r.c, code, uintptr(unsafe.Pointer(&b[0])))
293 runtime.KeepAlive(b)
294}
295
296// implMarker is a non-nil implementation pointer. libwayland only passes it
297// back to our dispatcher.
298func implMarker() uintptr {
299 if marker == 0 {
300 tablesMu.Lock()
301 if err := tables.reserve(8); err != nil {
302 panic(err)
303 }
304 _, marker = tables.alloc(8)
305 tablesMu.Unlock()
306 }
307 return marker
308}
309
310var marker uintptr
311
312// --- C callbacks (display goroutine) ---
313
314func dispatch(_ uintptr, target uintptr, opcode uint32, msg unsafe.Pointer, args unsafe.Pointer) int32 {
315 r := live.resources[target]
316 sig := msgSignature(msg)
317 if r == nil || r.handler == nil {
318 closeRequestFDs(sig, args)
319 return 0
320 }
321 n := argCount(sig)
322 var a []Arg
323 if n > 0 {
324 a = append([]Arg(nil), unsafe.Slice((*Arg)(args), n)...)
325 }
326 r.handler(r, opcode, a)
327 return 0
328}
329
330func destroyed(res uintptr) {
331 r := live.resources[res]
332 delete(live.resources, res)
333 if r == nil {
334 return
335 }
336 r.gone = true
337 if r.OnDestroy != nil {
338 r.OnDestroy()
339 }
340}
341
342// wake drains a Display's eventfd (wl_event_loop_fd_func_t).
343func wake(fd int32, mask uint32, data uintptr) int32 {
344 var buf [8]byte
345 _, _ = unix.Read(int(fd), buf[:])
346 return 0
347}
348
349func bind(client uintptr, data uintptr, version uint32, id uint32) {
350 if fn := live.binds[data]; fn != nil {
351 fn(Client{client}, version, id)
352 }
353}
354
355// msgSignature reads wl_message.signature.
356func msgSignature(msg unsafe.Pointer) string {
357 p := *(*unsafe.Pointer)(unsafe.Add(msg, 8))
358 n := 0
359 for *(*byte)(unsafe.Add(p, n)) != 0 {
360 n++
361 }
362 return string(unsafe.Slice((*byte)(p), n))
363}
364
365// closeRequestFDs takes ownership of fd arguments when there is no handler.
366func closeRequestFDs(sig string, args unsafe.Pointer) {
367 i := 0
368 for _, ch := range sig {
369 if ch == '?' || (ch >= '0' && ch <= '9') {
370 continue
371 }
372 if ch == 'h' {
373 _ = unix.Close((*Arg)(unsafe.Add(args, i*8)).Fd())
374 }
375 i++
376 }
377}
378
379// argCount counts arguments in a libwayland signature ("?oii", "2n", ...).
380func argCount(sig string) int {
381 n := 0
382 for _, ch := range sig {
383 if ch != '?' && (ch < '0' || ch > '9') {
384 n++
385 }
386 }
387 return n
388}