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