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