1package wlturbo
2
3import (
4 "encoding/binary"
5 "errors"
6
7 // "log"
8 "sync"
9 "sync/atomic"
10)
11
12// Context provides a compatibility layer for wl.Context
13type Context struct {
14 display *Display
15 proxies sync.Map // map[uint32]Proxy
16 closed atomic.Bool
17}
18
19// Proxy interface for Wayland protocol objects
20type Proxy interface {
21 Object
22 SetID(uint32)
23 Context() *Context
24 Dispatch(*Event)
25}
26
27// BaseProxy provides base implementation for protocol objects
28type BaseProxy struct {
29 id uint32
30 context *Context
31}
32
33// Event represents a Wayland protocol event
34type Event struct {
35 ProxyID uint32
36 Opcode uint16
37 data []byte
38 offset int
39}
40
41// Data returns the raw event data
42func (e *Event) Data() []byte {
43 return e.data
44}
45
46// Offset returns the current read offset
47func (e *Event) Offset() int {
48 return e.offset
49}
50
51// NewContext creates a new context from a display
52func NewContext(display *Display) *Context {
53 return &Context{
54 display: display,
55 }
56}
57
58// SendRequest sends a request through the context
59func (c *Context) SendRequest(proxy Proxy, opcode uint32, args ...interface{}) error {
60 if c.closed.Load() {
61 return errors.New("context is closed")
62 }
63 return c.display.SendRequest(proxy.ID(), uint16(opcode), args...)
64}
65
66// SendRequestWithFDs sends a request with file descriptors through the context
67func (c *Context) SendRequestWithFDs(proxy Proxy, opcode uint32, fds []int, args ...interface{}) error {
68 if c.closed.Load() {
69 return errors.New("context is closed")
70 }
71 return c.display.SendRequestWithFDs(proxy.ID(), uint16(opcode), fds, args...)
72}
73
74// Register registers a proxy object
75func (c *Context) Register(proxy Proxy) {
76 if proxy != nil && proxy.ID() != 0 {
77 c.proxies.Store(proxy.ID(), proxy)
78 c.display.objects.Store(proxy.ID(), proxy)
79 // Debug large IDs
80 // if proxy.ID() > 1000000 {
81 // log.Printf("wlclient: WARNING: Registering proxy with suspicious ID %d (0x%x)", proxy.ID(), proxy.ID())
82 // } else if proxy.ID() == 5 {
83 // log.Printf("wlclient: Registered output manager proxy with ID 5")
84 // }
85 }
86}
87
88// Unregister removes a proxy object
89func (c *Context) Unregister(proxy Proxy) {
90 if proxy != nil {
91 c.proxies.Delete(proxy.ID())
92 c.display.objects.Delete(proxy.ID())
93 }
94}
95
96// UnregisterID removes a proxy object by ID (overloaded for compatibility)
97func (c *Context) UnregisterID(id uint32) {
98 c.proxies.Delete(id)
99 c.display.objects.Delete(id)
100}
101
102// AllocateID allocates a new object ID
103func (c *Context) AllocateID() uint32 {
104 return c.display.AllocateID()
105}
106
107// Close closes the context
108func (c *Context) Close() error {
109 c.closed.Store(true)
110 return c.display.Close()
111}
112
113// RunTill runs the event loop until the callback fires
114func (c *Context) RunTill(callback Object) error {
115 if c.closed.Load() {
116 return errors.New("context is closed")
117 }
118
119 // Special case: if callback is a sync object, do a roundtrip
120 if _, ok := callback.(*callbackObject); ok {
121 return c.display.Roundtrip()
122 }
123
124 // Otherwise, process events until the callback is unregistered
125 // This assumes the callback will unregister itself when done
126 for {
127 if err := c.display.Dispatch(); err != nil {
128 return err
129 }
130
131 // Check if callback is still registered
132 if _, ok := c.proxies.Load(callback.ID()); !ok {
133 // Callback has been unregistered, we're done
134 return nil
135 }
136 }
137}
138
139// BaseProxy methods
140
141// ID returns the proxy's object ID
142func (p *BaseProxy) ID() uint32 {
143 return p.id
144}
145
146// SetId sets the proxy's object ID
147func (p *BaseProxy) SetID(id uint32) {
148 p.id = id
149}
150
151// Context returns the proxy's context
152func (p *BaseProxy) Context() *Context {
153 return p.context
154}
155
156// SetContext sets the proxy's context
157func (p *BaseProxy) SetContext(ctx *Context) {
158 p.context = ctx
159}
160
161// Dispatch default implementation (does nothing)
162func (p *BaseProxy) Dispatch(event *Event) {
163 // Default implementation does nothing
164}
165
166// Event methods for extracting data
167
168// Uint32 reads a uint32 from the event
169func (e *Event) Uint32() uint32 {
170 if e.offset+4 > len(e.data) {
171 return 0
172 }
173 val := binary.LittleEndian.Uint32(e.data[e.offset:])
174 e.offset += 4
175 return val
176}
177
178// Int32 reads an int32 from the event
179func (e *Event) Int32() int32 {
180 return int32(e.Uint32())
181}
182
183// Fixed reads a fixed-point value from the event
184func (e *Event) Fixed() Fixed {
185 return Fixed(e.Int32())
186}
187
188// String reads a string from the event
189func (e *Event) String() string {
190 if e.offset+4 > len(e.data) {
191 return ""
192 }
193 strlen := e.Uint32()
194 if strlen == 0 || e.offset+int(strlen) > len(e.data) {
195 return ""
196 }
197 // String includes null terminator in length
198 str := string(e.data[e.offset : e.offset+int(strlen)-1])
199 // Advance offset including padding
200 totalLen := strlen
201 padding := (4 - (totalLen % 4)) % 4
202 e.offset += int(totalLen + padding)
203 return str
204}
205
206// Array reads a byte array from the event
207func (e *Event) Array() []byte {
208 if e.offset+4 > len(e.data) {
209 return nil
210 }
211 arrlen := e.Uint32()
212 if arrlen == 0 || e.offset+int(arrlen) > len(e.data) {
213 return nil
214 }
215 arr := make([]byte, arrlen)
216 copy(arr, e.data[e.offset:e.offset+int(arrlen)])
217 // Advance offset including padding
218 padding := (4 - (arrlen % 4)) % 4
219 e.offset += int(arrlen + padding)
220 return arr
221}
222
223// Fd reads a file descriptor from the event
224func (e *Event) Fd() uintptr {
225 // File descriptors are passed out-of-band via SCM_RIGHTS
226 // We read them from our lock-free queue
227 if fd, ok := GetNextFD(); ok {
228 return uintptr(fd)
229 }
230 // Fallback: read placeholder value from message
231 _ = e.Uint32()
232 return 0
233}
234
235// NewId reads a new object ID from the event
236func (e *Event) NewID() Proxy {
237 id := e.Uint32()
238 // Return a basic proxy with just the ID
239 return &BaseProxy{id: id}
240}
241
242// Proxy reads an existing proxy reference from the event
243func (e *Event) Proxy() Proxy {
244 id := e.Uint32()
245 // Would need context reference to look up actual proxy
246 return &BaseProxy{id: id}
247}
248
249// Registry handler interfaces for compatibility
250
251// RegistryGlobalHandler interface
252type RegistryGlobalHandler interface {
253 HandleRegistryGlobal(event RegistryGlobalEvent)
254}
255
256// RegistryGlobalRemoveHandler interface
257type RegistryGlobalRemoveHandler interface {
258 HandleRegistryGlobalRemove(event RegistryGlobalRemoveEvent)
259}
260
261// RegistryGlobalEvent represents a registry global announcement
262type RegistryGlobalEvent struct {
263 Registry *Registry
264 Name uint32
265 Interface string
266 Version uint32
267}
268
269// RegistryGlobalRemoveEvent represents a registry global removal
270type RegistryGlobalRemoveEvent struct {
271 Registry *Registry
272 Name uint32
273}
274
275// AddGlobalHandler adds a global handler to the registry
276func (r *Registry) AddGlobalHandler(handler RegistryGlobalHandler) {
277 // Store the handler to be called for ALL globals
278 r.mu.Lock()
279 defer r.mu.Unlock()
280
281 // Initialize handlers map if needed
282 if r.handlers == nil {
283 r.handlers = make(map[string]GlobalHandler)
284 }
285
286 r.AddHandler("*", func(registry *Registry, name uint32, version uint32) {
287 global, ok := r.FindGlobalByName(name)
288 if ok {
289 event := RegistryGlobalEvent{
290 Registry: r,
291 Name: name,
292 Interface: global.Interface,
293 Version: version,
294 }
295 handler.HandleRegistryGlobal(event)
296 }
297 })
298}
299
300// AddGlobalRemoveHandler adds a global remove handler (placeholder)
301func (r *Registry) AddGlobalRemoveHandler(handler RegistryGlobalRemoveHandler) {
302 // Would need to implement removal handling
303}
304
305// FindGlobalByName finds a global by its name ID
306func (r *Registry) FindGlobalByName(name uint32) (Global, bool) {
307 r.mu.RLock()
308 defer r.mu.RUnlock()
309
310 if global, ok := r.globals[name]; ok {
311 return global, true
312 }
313 return Global{}, false
314}
315
316// Display compatibility methods
317
318// Context returns a context for this display
319func (d *Display) Context() *Context {
320 return d.context
321}
322
323// GetRegistry returns the registry (compatibility)
324func (d *Display) GetRegistry() *Registry {
325 return d.registry
326}
327
328// Sync creates a sync callback
329func (d *Display) Sync() (Object, error) {
330 callbackID := d.allocateID()
331 callback := &callbackObject{
332 BaseProxy: BaseProxy{
333 context: d.context,
334 id: callbackID,
335 },
336 display: d,
337 }
338
339 // Send sync request (opcode 0)
340 if err := d.SendRequest(1, 0, callbackID); err != nil {
341 return nil, err
342 }
343
344 // Store the callback object
345 d.objects.Store(callbackID, callback)
346
347 return callback, nil
348}
349
350// Additional protocol object types needed for compatibility
351
352// Seat capability constants
353const (
354 SeatCapabilityPointer = 1
355 SeatCapabilityKeyboard = 2
356 SeatCapabilityTouch = 4
357)
358
359// Seat represents a wl_seat
360type Seat struct {
361 BaseProxy
362 capabilities uint32
363 name string
364}
365
366// SeatCapabilitiesHandler handles seat capabilities events
367type SeatCapabilitiesHandler interface {
368 HandleSeatCapabilities(seat *Seat, capabilities uint32)
369}
370
371// SeatNameHandler handles seat name events
372type SeatNameHandler interface {
373 HandleSeatName(seat *Seat, name string)
374}
375
376// NewSeat creates a new seat proxy
377func NewSeat(ctx *Context) *Seat {
378 return &Seat{
379 BaseProxy: BaseProxy{
380 context: ctx,
381 },
382 }
383}
384
385// GetPointer gets the pointer device
386func (s *Seat) GetPointer() (*Pointer, error) {
387 pointer := &Pointer{
388 BaseProxy: BaseProxy{
389 context: s.context,
390 id: s.context.display.allocateID(),
391 },
392 }
393
394 // Register pointer before sending request
395 s.context.Register(pointer)
396
397 // Send get_pointer request (opcode 0)
398 err := s.context.SendRequest(s, 0, pointer.id)
399 if err != nil {
400 s.context.Unregister(pointer)
401 return nil, err
402 }
403
404 return pointer, nil
405}
406
407// GetKeyboard gets the keyboard device
408func (s *Seat) GetKeyboard() (*Keyboard, error) {
409 keyboard := &Keyboard{
410 BaseProxy: BaseProxy{
411 context: s.context,
412 id: s.context.display.allocateID(),
413 },
414 }
415
416 // Register keyboard before sending request
417 s.context.Register(keyboard)
418
419 // Send get_keyboard request (opcode 1)
420 err := s.context.SendRequest(s, 1, keyboard.id)
421 if err != nil {
422 s.context.Unregister(keyboard)
423 return nil, err
424 }
425
426 return keyboard, nil
427}
428
429// GetTouch gets the touch device
430func (s *Seat) GetTouch() (*Touch, error) {
431 touch := &Touch{
432 BaseProxy: BaseProxy{
433 context: s.context,
434 id: s.context.display.allocateID(),
435 },
436 }
437
438 // Register touch before sending request
439 s.context.Register(touch)
440
441 // Send get_touch request (opcode 2)
442 err := s.context.SendRequest(s, 2, touch.id)
443 if err != nil {
444 s.context.Unregister(touch)
445 return nil, err
446 }
447
448 return touch, nil
449}
450
451// Release releases the seat
452func (s *Seat) Release() error {
453 err := s.context.SendRequest(s, 3) // opcode 3
454 if err == nil {
455 s.context.Unregister(s)
456 }
457 return err
458}
459
460// Capabilities returns the seat capabilities
461func (s *Seat) Capabilities() uint32 {
462 return s.capabilities
463}
464
465// Name returns the seat name
466func (s *Seat) Name() string {
467 return s.name
468}
469
470// Dispatch handles events for the seat
471func (s *Seat) Dispatch(event *Event) {
472 // Handle seat events
473 switch event.Opcode {
474 case 0: // capabilities
475 s.capabilities = event.Uint32()
476 // TODO: Call registered handlers
477 case 1: // name
478 s.name = event.String()
479 // TODO: Call registered handlers
480 }
481}
482
483// Surface represents a wl_surface
484type Surface struct {
485 BaseProxy
486}
487
488// NewSurface creates a new surface proxy
489func NewSurface(ctx *Context) *Surface {
490 return &Surface{
491 BaseProxy: BaseProxy{
492 context: ctx,
493 },
494 }
495}
496
497// Destroy destroys the surface
498func (s *Surface) Destroy() error {
499 err := s.context.SendRequest(s, 0) // opcode 0
500 if err == nil {
501 s.context.Unregister(s)
502 }
503 return err
504}
505
506// Attach attaches a buffer to the surface
507func (s *Surface) Attach(buffer Object, x, y int32) error {
508 return s.context.SendRequest(s, 1, buffer, x, y) // opcode 1
509}
510
511// Damage marks a region of the surface as damaged
512func (s *Surface) Damage(x, y, width, height int32) error {
513 return s.context.SendRequest(s, 2, x, y, width, height) // opcode 2
514}
515
516// Frame requests a frame callback
517func (s *Surface) Frame() (Object, error) {
518 callback := &callbackObject{
519 BaseProxy: BaseProxy{
520 context: s.context,
521 id: s.context.display.allocateID(),
522 },
523 display: s.context.display,
524 }
525 s.context.Register(callback)
526
527 err := s.context.SendRequest(s, 3, callback.id) // opcode 3
528 if err != nil {
529 s.context.Unregister(callback)
530 return nil, err
531 }
532
533 return callback, nil
534}
535
536// SetOpaqueRegion sets the opaque region
537func (s *Surface) SetOpaqueRegion(region *Region) error {
538 return s.context.SendRequest(s, 4, region) // opcode 4
539}
540
541// SetInputRegion sets the input region
542func (s *Surface) SetInputRegion(region *Region) error {
543 return s.context.SendRequest(s, 5, region) // opcode 5
544}
545
546// Commit commits pending surface state
547func (s *Surface) Commit() error {
548 return s.context.SendRequest(s, 6) // opcode 6
549}
550
551// SetBufferTransform sets the buffer transform
552func (s *Surface) SetBufferTransform(transform int32) error {
553 return s.context.SendRequest(s, 7, transform) // opcode 7
554}
555
556// SetBufferScale sets the buffer scale
557func (s *Surface) SetBufferScale(scale int32) error {
558 return s.context.SendRequest(s, 8, scale) // opcode 8
559}
560
561// DamageBuffer marks a region of the buffer as damaged
562func (s *Surface) DamageBuffer(x, y, width, height int32) error {
563 return s.context.SendRequest(s, 9, x, y, width, height) // opcode 9
564}
565
566// Offset sets the buffer offset
567func (s *Surface) Offset(x, y int32) error {
568 return s.context.SendRequest(s, 10, x, y) // opcode 10
569}
570
571// Dispatch handles surface events
572func (s *Surface) Dispatch(event *Event) {
573 // Surface can receive enter/leave/preferred_buffer_scale/preferred_buffer_transform events
574 // But for now we don't handle them
575}
576
577// Pointer represents a wl_pointer
578type Pointer struct {
579 BaseProxy
580}
581
582// Keyboard represents a wl_keyboard
583type Keyboard struct {
584 BaseProxy
585}
586
587// Touch represents a wl_touch
588type Touch struct {
589 BaseProxy
590}
591
592// Output represents a wl_output
593type Output struct {
594 BaseProxy
595}
596
597// Region represents a wl_region
598type Region struct {
599 BaseProxy
600}
601
602// Add adds a rectangle to the region
603func (r *Region) Add(x, y, width, height int32) error {
604 return r.context.SendRequest(r, 0, x, y, width, height) // opcode 0
605}
606
607// Subtract subtracts a rectangle from the region
608func (r *Region) Subtract(x, y, width, height int32) error {
609 return r.context.SendRequest(r, 1, x, y, width, height) // opcode 1
610}
611
612// Destroy destroys the region
613func (r *Region) Destroy() error {
614 err := r.context.SendRequest(r, 2) // opcode 2
615 if err == nil {
616 r.context.Unregister(r)
617 }
618 return err
619}
620
621// Compositor represents a wl_compositor
622type Compositor struct {
623 BaseProxy
624}
625
626// NewCompositor creates a new compositor proxy
627func NewCompositor(ctx *Context) *Compositor {
628 return &Compositor{
629 BaseProxy: BaseProxy{
630 context: ctx,
631 },
632 }
633}
634
635// CreateSurface creates a new surface
636func (c *Compositor) CreateSurface() (*Surface, error) {
637 surface := &Surface{
638 BaseProxy: BaseProxy{
639 context: c.context,
640 id: c.context.display.allocateID(),
641 },
642 }
643
644 // Register surface before sending request
645 c.context.Register(surface)
646
647 // Send create_surface request (opcode 0)
648 err := c.context.SendRequest(c, 0, surface.id)
649 if err != nil {
650 c.context.Unregister(surface)
651 return nil, err
652 }
653
654 return surface, nil
655}
656
657// CreateRegion creates a new region
658func (c *Compositor) CreateRegion() (*Region, error) {
659 region := &Region{
660 BaseProxy: BaseProxy{
661 context: c.context,
662 id: c.context.display.allocateID(),
663 },
664 }
665
666 // Register region before sending request
667 c.context.Register(region)
668
669 // Send create_region request (opcode 1)
670 err := c.context.SendRequest(c, 1, region.id)
671 if err != nil {
672 c.context.Unregister(region)
673 return nil, err
674 }
675
676 return region, nil
677}