1package wlturbo
2
3import (
4 "encoding/binary"
5 "errors"
6 "fmt"
7 "sync"
8 "sync/atomic"
9
10 "golang.org/x/sys/unix"
11)
12
13// Context provides a compatibility layer for wl.Context
14type Context struct {
15 display *Display
16 proxies sync.Map // map[uint32]Proxy
17 closed atomic.Bool
18}
19
20// Proxy interface for Wayland protocol objects
21type Proxy interface {
22 Object
23 SetID(uint32)
24 Context() *Context
25 Dispatch(*Event)
26}
27
28// BaseProxy provides base implementation for protocol objects
29type BaseProxy struct {
30 id uint32
31 context *Context
32 version uint32
33}
34
35// eventPool recycles Events across dispatches; an Event is only valid for
36// the duration of the handler call.
37var eventPool = sync.Pool{New: func() any { return &Event{} }}
38
39// Event represents a Wayland protocol event
40type Event struct {
41 ProxyID uint32
42 Opcode uint16
43 data []byte
44 offset int
45 display *Display
46 fds []*OwnedFD
47 fdIndex int
48}
49
50// Data returns the raw event body. It aliases the connection's receive buffer
51// and is only valid until the handler returns; copy it to keep it.
52func (e *Event) Data() []byte {
53 return e.data
54}
55
56// Offset returns the current read offset
57func (e *Event) Offset() int {
58 return e.offset
59}
60
61// NewContext creates a new context from a display
62func NewContext(display *Display) *Context {
63 return &Context{
64 display: display,
65 }
66}
67
68// SendRequest sends a request through the context. The proxy is validated
69// under the connection send lock, so no request reaches the wire after a
70// destructor for the same proxy.
71func (c *Context) SendRequest(proxy Proxy, opcode uint32, args ...interface{}) error {
72 return c.SendRequestWithFDs(proxy, opcode, nil, args...)
73}
74
75// SendRequestWithFDs sends a request with file descriptors through the context.
76// On error the FDs remain owned by the caller.
77func (c *Context) SendRequestWithFDs(proxy Proxy, opcode uint32, fds []int, args ...interface{}) error {
78 return c.sendChecked(proxy, opcode, fds, c.CheckProxy, args, nil)
79}
80
81// CheckProxy rejects stale and foreign proxies before any bytes are written.
82func (c *Context) CheckProxy(proxy Proxy) error {
83 if c.closed.Load() {
84 return errors.New("context is closed")
85 }
86 if proxy == nil || proxy.ID() == 0 || proxy.Context() != c {
87 return errors.New("invalid proxy")
88 }
89 registered, ok := c.proxies.Load(proxy.ID())
90 if !ok || registered != proxy {
91 return errors.New("proxy is not registered")
92 }
93 return nil
94}
95
96// Request describes one protocol request for Context.Request and
97// Context.RequestArgs.
98type Request struct {
99 Proxy Proxy
100 Opcode uint32
101 Name string // interface.request, used in errors
102 Since uint32 // version that introduced the request; 0 or 1 means always
103 Destructor bool // claim the proxy exactly once, as SendDestructor does
104 Child Proxy // new_id object created by this request, or nil
105 FDs []int // descriptors to attach; closed after a successful send
106}
107
108// Request runs one request with its whole lifecycle: it checks the proxy and
109// version, allocates and registers Child (inheriting the parent's version),
110// sends, and then either closes the sent descriptors or, on error,
111// unregisters Child and leaves the descriptors with the caller. Child must
112// already have this context; it is passed in args at its wire position.
113//
114// Every argument is boxed into an interface, and a descriptor is a uintptr
115// placeholder that r.FDs is not checked against. Generated bindings use
116// RequestArgs instead, which marshals the same bytes without allocating.
117func (c *Context) Request(r Request, args ...interface{}) error {
118 return c.request(r, args, nil, false)
119}
120
121// RequestArgs is Request with typed arguments: same lifecycle, same wire
122// bytes, no per-argument interface boxing. Child, when set, must be passed as
123// an ArgObject at its wire position. The number of ArgFD markers must equal
124// len(r.FDs); a mismatch fails before the child is allocated, the send guard
125// runs or anything is written, and leaves the descriptors with the caller.
126func (c *Context) RequestArgs(r Request, args ...Arg) error {
127 return c.request(r, nil, args, true)
128}
129
130func (c *Context) request(r Request, args []interface{}, vals []Arg, typed bool) error {
131 if typed {
132 markers := 0
133 for i := range vals {
134 if vals[i].kind == argFD {
135 markers++
136 }
137 }
138 if markers != len(r.FDs) {
139 return fmt.Errorf("wlturbo: %s: %d fd arguments but %d descriptors", r.Name, markers, len(r.FDs))
140 }
141 }
142 if err := c.CheckProxy(r.Proxy); err != nil {
143 return err
144 }
145 if v, ok := r.Proxy.(interface{ Version() uint32 }); ok {
146 if err := CheckVersion(v.Version(), r.Since, r.Name); err != nil {
147 return err
148 }
149 }
150 if r.Child != nil {
151 r.Child.SetID(c.AllocateID())
152 if v, ok := r.Proxy.(interface{ Version() uint32 }); ok {
153 if s, ok := r.Child.(interface{ SetVersion(uint32) }); ok {
154 s.SetVersion(v.Version())
155 }
156 }
157 c.Register(r.Child)
158 }
159 var err error
160 if r.Destructor {
161 err = c.sendChecked(r.Proxy, r.Opcode, r.FDs, c.claimDestroy, args, vals)
162 } else {
163 err = c.sendChecked(r.Proxy, r.Opcode, r.FDs, c.CheckProxy, args, vals)
164 }
165 if err != nil {
166 if r.Child != nil {
167 c.Unregister(r.Child)
168 }
169 return err
170 }
171 for _, fd := range r.FDs {
172 _ = CloseSentFD(fd)
173 }
174 return nil
175}
176
177// sendChecked validates the proxy, then sends with check run under the send
178// lock (CheckProxy for plain requests, claimDestroy for destructors).
179func (c *Context) sendChecked(proxy Proxy, opcode uint32, fds []int, check func(Proxy) error, args []interface{}, vals []Arg) error {
180 if err := c.CheckProxy(proxy); err != nil {
181 return err
182 }
183 return c.display.sendRequest(proxy.ID(), uint16(opcode), fds, func() error { return check(proxy) }, args, vals)
184}
185
186// SendDestructor sends a destructor request exactly once. The proxy is
187// claimed (unregistered) under the connection send lock, after marshaling
188// and immediately before the write: concurrent destructors and later requests
189// on the proxy fail without writing. A marshaling error leaves the proxy
190// registered. A write error after the claim leaves the proxy unregistered;
191// such an error means the connection is broken and must be closed.
192func (c *Context) SendDestructor(proxy Proxy, opcode uint32, args ...interface{}) error {
193 return c.SendDestructorWithFDs(proxy, opcode, nil, args...)
194}
195
196// SendDestructorWithFDs is SendDestructor for requests carrying FDs. On error
197// the FDs remain owned by the caller.
198func (c *Context) SendDestructorWithFDs(proxy Proxy, opcode uint32, fds []int, args ...interface{}) error {
199 return c.sendChecked(proxy, opcode, fds, c.claimDestroy, args, nil)
200}
201
202func (c *Context) claimDestroy(proxy Proxy) error {
203 if err := c.CheckProxy(proxy); err != nil {
204 return err
205 }
206 if !c.proxies.CompareAndDelete(proxy.ID(), proxy) {
207 return errors.New("proxy is not registered")
208 }
209 // The compositor may already have sent events for this object. Keep a
210 // zombie until delete_id so those events are dropped, not fatal.
211 c.display.objects.CompareAndSwap(proxy.ID(), proxy, &zombie{object: proxy})
212 return nil
213}
214
215// Abandon stops delivering events to proxy without sending any request. Use
216// it for objects the compositor destroys itself, such as a wl_callback whose
217// done event may still be in flight: the object stays a zombie, so a late
218// event is discarded and the compositor's wl_display.delete_id retires the ID.
219// It returns an error when proxy is not registered on this context.
220func (c *Context) Abandon(proxy Proxy) error {
221 return c.claimDestroy(proxy)
222}
223
224// zombie stands in for an object the client destroyed until the compositor
225// acknowledges the destruction with wl_display.delete_id. Events that were in
226// flight for it are discarded; their descriptors are closed.
227type zombie struct{ object Object }
228
229func (z *zombie) ID() uint32 { return z.object.ID() }
230
231// Register registers a proxy object
232func (c *Context) Register(proxy Proxy) {
233 if proxy != nil && proxy.ID() != 0 && !c.closed.Load() {
234 if proxy.Context() != c {
235 return
236 }
237 c.proxies.Store(proxy.ID(), proxy)
238 c.display.objects.Store(proxy.ID(), proxy)
239 }
240}
241
242// Unregister removes a proxy object
243// Only this proxy is removed: a zombie or a newer object that took the ID
244// is left in place.
245func (c *Context) Unregister(proxy Proxy) {
246 if proxy != nil {
247 c.proxies.CompareAndDelete(proxy.ID(), proxy)
248 c.display.objects.CompareAndDelete(proxy.ID(), proxy)
249 }
250}
251
252// UnregisterID removes a proxy object by ID (overloaded for compatibility)
253func (c *Context) UnregisterID(id uint32) {
254 c.proxies.Delete(id)
255 c.display.objects.Delete(id)
256}
257
258// AllocateID allocates a new object ID
259func (c *Context) AllocateID() uint32 {
260 return c.display.AllocateID()
261}
262
263// Close closes the context
264func (c *Context) Close() error {
265 c.closed.Store(true)
266 return c.display.Close()
267}
268
269// RunTill runs the event loop until the callback fires
270func (c *Context) RunTill(callback Object) error {
271 if c.closed.Load() {
272 return errors.New("context is closed")
273 }
274
275 // Special case: if callback is a sync object, do a roundtrip
276 if _, ok := callback.(*callbackObject); ok {
277 return c.display.Roundtrip()
278 }
279
280 // Otherwise, process events until the callback is unregistered
281 // This assumes the callback will unregister itself when done
282 for {
283 if err := c.display.Dispatch(); err != nil {
284 return err
285 }
286
287 // Check if callback is still registered
288 if _, ok := c.proxies.Load(callback.ID()); !ok {
289 // Callback has been unregistered, we're done
290 return nil
291 }
292 }
293}
294
295// BaseProxy methods
296
297// ID returns the proxy's object ID
298func (p *BaseProxy) ID() uint32 {
299 return p.id
300}
301
302// SetId sets the proxy's object ID
303func (p *BaseProxy) SetID(id uint32) {
304 p.id = id
305}
306
307// Context returns the proxy's context
308func (p *BaseProxy) Context() *Context {
309 return p.context
310}
311
312// SetContext sets the proxy's context
313func (p *BaseProxy) SetContext(ctx *Context) {
314 p.context = ctx
315}
316
317// Version returns the protocol version the object was bound or created at,
318// or 0 when it is unknown (for example after Registry.BindID).
319func (p *BaseProxy) Version() uint32 {
320 return p.version
321}
322
323// SetVersion records the object's protocol version. Registry.Bind sets it for
324// globals and generated requests copy it from parent to child.
325func (p *BaseProxy) SetVersion(v uint32) {
326 p.version = v
327}
328
329// ErrVersionTooLow reports a request the bound object version does not have.
330var ErrVersionTooLow = errors.New("wlturbo: request needs a newer object version")
331
332// CheckVersion rejects a request introduced in version since when the
333// object's known version is lower. An unknown version (0) is not checked.
334func CheckVersion(version, since uint32, request string) error {
335 if version != 0 && version < since {
336 return fmt.Errorf("%w: %s needs version %d, object has %d", ErrVersionTooLow, request, since, version)
337 }
338 return nil
339}
340
341// Dispatch default implementation (does nothing)
342func (p *BaseProxy) Dispatch(event *Event) {
343 // Default implementation does nothing
344}
345
346// Event methods for extracting data
347
348// Uint32 reads a uint32 from the event
349func (e *Event) Uint32() uint32 {
350 if e.offset+4 > len(e.data) {
351 return 0
352 }
353 val := binary.LittleEndian.Uint32(e.data[e.offset:])
354 e.offset += 4
355 return val
356}
357
358// Int32 reads an int32 from the event
359func (e *Event) Int32() int32 {
360 return int32(e.Uint32())
361}
362
363// Fixed reads a fixed-point value from the event
364func (e *Event) Fixed() Fixed {
365 return Fixed(e.Int32())
366}
367
368// String reads a string from the event
369func (e *Event) String() string {
370 if e.offset+4 > len(e.data) {
371 return ""
372 }
373 strlen := e.Uint32()
374 if strlen == 0 || e.offset+int(strlen) > len(e.data) {
375 return ""
376 }
377 // String includes null terminator in length
378 str := string(e.data[e.offset : e.offset+int(strlen)-1])
379 // Advance offset including padding
380 totalLen := strlen
381 padding := (4 - (totalLen % 4)) % 4
382 e.offset += int(totalLen + padding)
383 return str
384}
385
386// Array reads a byte array from the event
387func (e *Event) Array() []byte {
388 if e.offset+4 > len(e.data) {
389 return nil
390 }
391 arrlen := e.Uint32()
392 if arrlen == 0 || e.offset+int(arrlen) > len(e.data) {
393 return nil
394 }
395 arr := make([]byte, arrlen)
396 copy(arr, e.data[e.offset:e.offset+int(arrlen)])
397 // Advance offset including padding
398 padding := (4 - (arrlen % 4)) % 4
399 e.offset += int(arrlen + padding)
400 return arr
401}
402
403// OwnedFD owns one received descriptor. Close is idempotent; Take transfers
404// ownership to the caller (who must close it). Descriptor zero is valid.
405type OwnedFD struct {
406 mu sync.Mutex
407 fd int
408}
409
410func (f *OwnedFD) Take() (int, error) {
411 if f == nil {
412 return -1, errors.New("missing descriptor")
413 }
414 f.mu.Lock()
415 defer f.mu.Unlock()
416 if f.fd < 0 {
417 return -1, errors.New("descriptor already taken or closed")
418 }
419 n := f.fd
420 f.fd = -1
421 return n, nil
422}
423func (f *OwnedFD) Close() error {
424 if f == nil {
425 return nil
426 }
427 f.mu.Lock()
428 defer f.mu.Unlock()
429 if f.fd < 0 {
430 return nil
431 }
432 n := f.fd
433 f.fd = -1
434 return unix.Close(n)
435}
436
437// FD transfers ownership of the next descriptor assigned to this event.
438// An unclaimed descriptor is closed when dispatch completes.
439func (e *Event) FD() *OwnedFD {
440 if e.fdIndex >= len(e.fds) {
441 return nil
442 }
443 fd := e.fds[e.fdIndex]
444 e.fds[e.fdIndex] = nil
445 e.fdIndex++
446 return fd
447}
448
449// Fd is the legacy raw descriptor API; prefer FD for explicit ownership.
450func (e *Event) Fd() uintptr {
451 fd := e.FD()
452 if fd == nil {
453 _ = e.Uint32()
454 return 0
455 }
456 n, _ := fd.Take()
457 return uintptr(n)
458}
459
460// NewId reads a new object ID from the event
461func (e *Event) NewID() Proxy {
462 id := e.Uint32()
463 // Return a basic proxy with just the ID
464 return &BaseProxy{id: id}
465}
466
467// Proxy reads an existing proxy reference from the event
468func (e *Event) Proxy() Proxy {
469 id := e.Uint32()
470 // Would need context reference to look up actual proxy
471 return &BaseProxy{id: id}
472}
473
474// Registry handler interfaces for compatibility
475
476// RegistryGlobalHandler interface
477type RegistryGlobalHandler interface {
478 HandleRegistryGlobal(event RegistryGlobalEvent)
479}
480
481// RegistryGlobalRemoveHandler interface
482type RegistryGlobalRemoveHandler interface {
483 HandleRegistryGlobalRemove(event RegistryGlobalRemoveEvent)
484}
485
486// RegistryGlobalEvent represents a registry global announcement
487type RegistryGlobalEvent struct {
488 Registry *Registry
489 Name uint32
490 Interface string
491 Version uint32
492}
493
494// RegistryGlobalRemoveEvent represents a registry global removal
495type RegistryGlobalRemoveEvent struct {
496 Registry *Registry
497 Name uint32
498}
499
500// AddGlobalHandler adds a global handler to the registry
501func (r *Registry) AddGlobalHandler(handler RegistryGlobalHandler) {
502 if handler == nil {
503 return
504 }
505 r.AddHandler("*", func(registry *Registry, name uint32, version uint32) {
506 if global, ok := r.FindGlobalByName(name); ok {
507 handler.HandleRegistryGlobal(RegistryGlobalEvent{Registry: r, Name: name, Interface: global.Interface, Version: version})
508 }
509 })
510}
511
512// AddGlobalRemoveHandler registers a handler for registry removals.
513func (r *Registry) AddGlobalRemoveHandler(handler RegistryGlobalRemoveHandler) {
514 if handler == nil {
515 return
516 }
517 r.mu.Lock()
518 r.removeHandlers = append(r.removeHandlers, handler)
519 r.mu.Unlock()
520}
521
522// FindGlobalByName finds a global by its name ID
523func (r *Registry) FindGlobalByName(name uint32) (Global, bool) {
524 r.mu.RLock()
525 defer r.mu.RUnlock()
526
527 if global, ok := r.globals[name]; ok {
528 return global, true
529 }
530 return Global{}, false
531}
532
533// Display compatibility methods
534
535// Context returns a context for this display
536func (d *Display) Context() *Context {
537 return d.context
538}
539
540// GetRegistry returns the registry (compatibility)
541func (d *Display) GetRegistry() *Registry {
542 return d.registry
543}
544
545// Sync creates a sync callback
546func (d *Display) Sync() (Object, error) {
547 callbackID := d.allocateID()
548 callback := &callbackObject{
549 BaseProxy: BaseProxy{
550 context: d.context,
551 id: callbackID,
552 },
553 display: d,
554 }
555
556 // Store before sending: the compositor may reply immediately.
557 d.context.Register(callback)
558 if err := d.SendRequest(1, 0, callbackID); err != nil {
559 d.context.Unregister(callback)
560 return nil, err
561 }
562
563 return callback, nil
564}