1// Package wlturbo provides a high-performance Wayland client implementation optimized for gaming and real-time applications.
2//
3// This package delivers sub-microsecond latency, zero-allocation hot paths, and support for 8000Hz gaming devices.
4// It's designed for video game engines, competitive gaming, and other performance-critical applications.
5package wlturbo
6
7import (
8 "bytes"
9 "encoding/binary"
10 "errors"
11 "fmt"
12 "io"
13 "log"
14
15 // "log"
16 "net"
17 "os"
18 "path/filepath"
19 "sync"
20 "sync/atomic"
21)
22
23// Pre-allocated buffer pool for performance
24var bufferPool = sync.Pool{
25 New: func() interface{} {
26 return &bytes.Buffer{}
27 },
28}
29
30// Fixed represents a 24.8 fixed-point number
31type Fixed int32
32
33// Float64 converts Fixed to float64
34func (f Fixed) Float64() float64 {
35 return float64(f) / 256.0
36}
37
38// NewFixed creates a Fixed from float64
39func NewFixed(v float64) Fixed {
40 return Fixed(v * 256.0)
41}
42
43// Object represents a Wayland object
44type Object interface {
45 ID() uint32
46}
47
48// Display represents a connection to the Wayland display
49type Display struct {
50 conn net.Conn
51 fd int
52 objects sync.Map // map[uint32]Object
53 nextID uint32
54 sendMu sync.Mutex
55 recvMu sync.Mutex
56 listeners sync.Map // map[uint32]map[uint16][]func([]byte)
57
58 // High-performance event dispatcher
59 dispatcher *EventDispatcher
60
61 // Core objects
62 registry *Registry
63 context *Context // Store context once
64
65 // Error state
66 lastError error
67 lastErrorCode uint32
68 lastErrorObj uint32
69
70 // Reusable read buffer for header
71 headerBuf [8]byte
72
73 // Pre-allocated buffer for event bodies (avoid allocations)
74 eventBodyBuf [4096]byte
75}
76
77// Registry represents the global registry
78type Registry struct {
79 id uint32
80 display *Display
81 globals map[uint32]Global
82 mu sync.RWMutex
83 handlers map[string]GlobalHandler
84}
85
86// callbackObject represents a wl_callback object
87type callbackObject struct {
88 BaseProxy
89 display *Display
90}
91
92func (c *callbackObject) ID() uint32 {
93 return c.id
94}
95
96// Dispatch handles callback events (opcode 0 = done)
97func (c *callbackObject) Dispatch(event *Event) {
98 if event.Opcode == 0 { // done event
99 log.Printf("wlclient: callbackObject.Dispatch fired for ID %d", c.id)
100 // Trigger any listeners for this callback
101 if listeners, ok := c.display.listeners.Load(c.id); ok {
102 if opcodeMap, ok := listeners.(*sync.Map); ok {
103 if handlers, ok := opcodeMap.Load(uint16(0)); ok {
104 if handlerSlice, ok := handlers.(*[]func([]byte)); ok {
105 if handlerSlice == nil {
106 log.Printf("wlclient: WARNING: handlerSlice is nil for callback ID %d", c.id)
107 return
108 }
109 log.Printf("wlclient: Found %d handlers for callback ID %d", len(*handlerSlice), c.id)
110 for i, handler := range *handlerSlice {
111 if handler == nil {
112 log.Printf("wlclient: WARNING: handler %d is nil for callback ID %d", i, c.id)
113 continue
114 }
115 log.Printf("wlclient: Calling handler %d for callback ID %d", i, c.id)
116 handler(event.data)
117 }
118 } else {
119 log.Printf("wlclient: WARNING: handlers not a slice for callback ID %d", c.id)
120 }
121 } else {
122 log.Printf("wlclient: WARNING: no handlers found for opcode 0 on callback ID %d", c.id)
123 }
124 } else {
125 log.Printf("wlclient: WARNING: listeners not a sync.Map for callback ID %d", c.id)
126 }
127 } else {
128 log.Printf("wlclient: WARNING: no listeners found for callback ID %d", c.id)
129 }
130 }
131}
132
133// Global represents a global object
134type Global struct {
135 Name uint32
136 Interface string
137 Version uint32
138}
139
140// GlobalHandler is called when a global is announced
141type GlobalHandler func(registry *Registry, name uint32, version uint32)
142
143// Connect connects to the Wayland display
144func Connect(socketPath string) (*Display, error) {
145 if socketPath == "" {
146 socketPath = os.Getenv("WAYLAND_DISPLAY")
147 if socketPath == "" {
148 socketPath = "wayland-0"
149 }
150 }
151
152 // Resolve socket path
153 if !filepath.IsAbs(socketPath) {
154 runDir := os.Getenv("XDG_RUNTIME_DIR")
155 if runDir == "" {
156 return nil, errors.New("XDG_RUNTIME_DIR not set")
157 }
158 socketPath = filepath.Join(runDir, socketPath)
159 }
160
161 // log.Printf("wlclient: Connecting to Wayland socket: %s", socketPath)
162 // Connect to socket
163 conn, err := net.Dial("unix", socketPath)
164 if err != nil {
165 return nil, fmt.Errorf("failed to connect to Wayland: %w", err)
166 }
167 // log.Printf("wlclient: Connected to Wayland socket successfully")
168
169 // Get file descriptor for advanced operations
170 file, err := conn.(*net.UnixConn).File()
171 if err != nil {
172 _ = conn.Close()
173 return nil, fmt.Errorf("failed to get socket fd: %w", err)
174 }
175 fd := int(file.Fd())
176 _ = file.Close() // We only need the fd
177
178 d := &Display{
179 conn: conn,
180 fd: fd,
181 nextID: 2, // 1 is reserved for wl_display
182 dispatcher: NewEventDispatcher(),
183 }
184
185 // Create context once and store it
186 d.context = NewContext(d)
187
188 // Register display object (ID 1)
189 d.objects.Store(uint32(1), d)
190
191 // Initialize registry
192 d.registry = &Registry{
193 id: d.allocateID(),
194 display: d,
195 globals: make(map[uint32]Global),
196 handlers: make(map[string]GlobalHandler),
197 }
198 d.objects.Store(d.registry.id, d.registry)
199
200 // Get registry
201 if err := d.getRegistry(); err != nil {
202 _ = conn.Close()
203 return nil, fmt.Errorf("failed to get registry: %w", err)
204 }
205
206 // Don't do initial roundtrip here - let the caller do it after setting up handlers
207 // log.Printf("wlclient: display connected, registry ready")
208
209 return d, nil
210}
211
212// Close closes the display connection
213func (d *Display) Close() error {
214 return d.conn.Close()
215}
216
217// ID returns the display's object ID (always 1)
218func (d *Display) ID() uint32 {
219 return 1
220}
221
222// RegisterEventHandler registers a high-performance event handler
223func (d *Display) RegisterEventHandler(objectID uint32, opcode uint16, handler EventHandler) {
224 d.dispatcher.RegisterHandler(objectID, opcode, handler)
225}
226
227// allocateID allocates a new object ID
228func (d *Display) allocateID() uint32 {
229 id := atomic.AddUint32(&d.nextID, 1) - 1
230 log.Printf("wlclient: allocateID() generated ID %d", id)
231 return id
232}
233
234// AllocateID allocates a new object ID (public method)
235func (d *Display) AllocateID() uint32 {
236 return d.allocateID()
237}
238
239// SendRequest sends a request to the compositor
240func (d *Display) SendRequest(objectID uint32, opcode uint16, args ...interface{}) error {
241 return d.SendRequestWithFDs(objectID, opcode, nil, args...)
242}
243
244// SendRequestWithFDs sends a request with file descriptors
245func (d *Display) SendRequestWithFDs(objectID uint32, opcode uint16, fds []int, args ...interface{}) error {
246 // Get buffer from pool
247 buf := bufferPool.Get().(*bytes.Buffer)
248 defer func() {
249 buf.Reset()
250 bufferPool.Put(buf)
251 }()
252
253 // Write header placeholder
254 header := make([]byte, 8)
255 _, _ = buf.Write(header)
256
257 // Marshal arguments
258 for _, arg := range args {
259 if err := d.marshalArg(buf, arg); err != nil {
260 return fmt.Errorf("failed to marshal argument: %w", err)
261 }
262 }
263
264 // Update header with actual size in bytes
265 bufLen := buf.Len()
266 if bufLen > 0xFFFF {
267 return fmt.Errorf("message too large: %d bytes", bufLen)
268 }
269 size := uint32(bufLen) // Safe: checked above
270 binary.LittleEndian.PutUint32(header[0:4], objectID)
271 // Upper 16 bits = size, lower 16 bits = opcode
272 binary.LittleEndian.PutUint32(header[4:8], (size<<16)|uint32(opcode))
273
274 // Update buffer with correct header
275 data := buf.Bytes()
276 copy(data[0:8], header)
277
278 // Send message with optional file descriptors
279 return d.sendmsgWithFDs(data, fds)
280}
281
282// marshalArg marshals a single argument
283func (d *Display) marshalArg(buf *bytes.Buffer, arg interface{}) error {
284 switch v := arg.(type) {
285 case uint32:
286 return binary.Write(buf, binary.LittleEndian, v)
287 case int32:
288 return binary.Write(buf, binary.LittleEndian, v)
289 case Fixed:
290 return binary.Write(buf, binary.LittleEndian, int32(v))
291 case string:
292 // String format: length (including null) + string + null + padding
293 strlen := len(v) + 1
294 if strlen > 0xFFFFFFFF {
295 return fmt.Errorf("string too long: %d bytes", strlen)
296 }
297 if err := binary.Write(buf, binary.LittleEndian, uint32(strlen)); err != nil { // Safe: checked above
298 return err
299 }
300 _, _ = buf.WriteString(v)
301 _ = buf.WriteByte(0)
302 // Pad to 32-bit boundary
303 padding := (4 - (strlen % 4)) % 4
304 for i := 0; i < padding; i++ {
305 _ = buf.WriteByte(0)
306 }
307 case []byte:
308 // Array format: length + data + padding
309 arrlen := len(v)
310 if arrlen > 0xFFFFFFFF {
311 return fmt.Errorf("array too long: %d bytes", arrlen)
312 }
313 if err := binary.Write(buf, binary.LittleEndian, uint32(arrlen)); err != nil {
314 return err
315 }
316 _, _ = buf.Write(v)
317 // Pad to 32-bit boundary
318 padding := (4 - (arrlen % 4)) % 4
319 for i := 0; i < padding; i++ {
320 _ = buf.WriteByte(0)
321 }
322 case Object:
323 if v != nil {
324 return binary.Write(buf, binary.LittleEndian, v.ID())
325 }
326 return binary.Write(buf, binary.LittleEndian, uint32(0))
327 case nil:
328 // Null object
329 return binary.Write(buf, binary.LittleEndian, uint32(0))
330 case int:
331 // File descriptor - write placeholder in message
332 // Actual FD will be sent via SCM_RIGHTS
333 return binary.Write(buf, binary.LittleEndian, uint32(0))
334 case uintptr:
335 // File descriptor as uintptr - NO placeholder in message for neurlang compatibility
336 // FD is ONLY sent via SCM_RIGHTS, not in the message body
337 return nil
338 default:
339 return fmt.Errorf("unsupported argument type: %T", arg)
340 }
341 return nil
342}
343
344// Dispatch reads and dispatches events
345func (d *Display) Dispatch() error {
346 d.recvMu.Lock()
347 defer d.recvMu.Unlock()
348
349 // Read header with potential file descriptors
350 n, fds, err := d.recvmsgWithFDs(d.headerBuf[:])
351 if err != nil {
352 return fmt.Errorf("failed to read header: %w", err)
353 }
354 if n < 8 {
355 return fmt.Errorf("incomplete header: got %d bytes", n)
356 }
357
358 objectID := binary.LittleEndian.Uint32(d.headerBuf[0:4])
359 sizeOpcode := binary.LittleEndian.Uint32(d.headerBuf[4:8])
360 // Upper 16 bits = size (includes header), lower 16 bits = opcode
361 size := sizeOpcode >> 16
362 opcode := sizeOpcode & 0xffff
363
364 // Only log suspicious events or important ones
365 // if objectID > 1000000 || objectID == 5 {
366 // log.Printf("wlclient: Dispatch received event: object=%d (0x%x), opcode=%d, size=%d", objectID, objectID, opcode, size)
367 // }
368 if opcode > 0xFFFF {
369 return fmt.Errorf("invalid opcode: %d", opcode)
370 }
371
372 // Read message body (size includes 8-byte header)
373 var body []byte
374 if size > 8 {
375 bodySize := size - 8
376
377 // Use pre-allocated buffer for small messages (zero allocation)
378 if bodySize <= uint32(len(d.eventBodyBuf)) {
379 body = d.eventBodyBuf[:bodySize]
380 } else {
381 // Large message, allocate new buffer
382 body = make([]byte, bodySize)
383 }
384
385 n, moreFds, err := d.recvmsgWithFDs(body)
386 if err != nil {
387 return fmt.Errorf("failed to read body: %w", err)
388 }
389 if n < int(bodySize) {
390 // Read remaining data if needed
391 remaining := body[n:]
392 if _, err := io.ReadFull(d.conn, remaining); err != nil {
393 return fmt.Errorf("failed to read remaining body: %w", err)
394 }
395 }
396 fds = append(fds, moreFds...)
397 }
398
399 // Handle display events specially
400 if objectID == 1 {
401 return d.handleDisplayEvent(uint16(opcode), body)
402 }
403
404 // Try to find the object
405 if obj, ok := d.objects.Load(objectID); ok {
406 // Only log for non-registry objects to reduce spam
407 if objectID != 2 {
408 log.Printf("wlclient: Found object for ID %d, type: %T", objectID, obj)
409 }
410
411 // CRITICAL: Handle server object creation BEFORE dispatching
412 if d.handleServerObject(objectID, uint16(opcode), body) {
413 log.Printf("wlclient: Handled server object creation for ID %d, opcode %d", objectID, opcode)
414 }
415
416 // Check if it's a Proxy with Dispatch method
417 if proxy, ok := obj.(Proxy); ok && proxy != nil {
418 event := &Event{
419 ProxyID: objectID,
420 Opcode: uint16(opcode),
421 data: body,
422 offset: 0,
423 }
424 if objectID != 2 {
425 log.Printf("wlclient: Dispatching event to proxy ID %d, opcode %d", objectID, opcode)
426 }
427 proxy.Dispatch(event)
428 return nil // Event was handled
429 }
430 } else {
431 log.Printf("wlclient: WARNING: No object found for ID %d (0x%x)", objectID, objectID)
432 }
433
434 // Use high-performance dispatcher
435 d.dispatcher.Dispatch(objectID, uint16(opcode), body)
436
437 // Also check listeners (for compatibility)
438 if listeners, ok := d.listeners.Load(objectID); ok {
439 if opcodeMap, ok := listeners.(*sync.Map); ok {
440 if handlers, ok := opcodeMap.Load(uint16(opcode)); ok {
441 if handlerSlice, ok := handlers.(*[]func([]byte)); ok {
442 for _, handler := range *handlerSlice {
443 handler(body)
444 }
445 }
446 }
447 }
448 }
449
450 return nil
451}
452
453// handleDisplayEvent handles events on the display object
454func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error {
455 switch opcode {
456 case 0: // error
457 if len(data) < 8 {
458 return errors.New("invalid error event")
459 }
460 objectID := binary.LittleEndian.Uint32(data[0:4])
461 code := binary.LittleEndian.Uint32(data[4:8])
462
463 var message string
464 if len(data) > 8 {
465 // Message is optional, parse if present
466 if len(data) >= 12 {
467 msgLen := binary.LittleEndian.Uint32(data[8:12])
468 if msgLen > 0 && len(data) >= 12+int(msgLen) {
469 message = string(data[12 : 12+msgLen-1]) // -1 to remove null terminator
470 }
471 }
472 }
473
474 d.lastError = fmt.Errorf("protocol error: object %d, code %d: %s", objectID, code, message)
475 d.lastErrorCode = code
476 d.lastErrorObj = objectID
477 return d.lastError
478
479 case 1: // delete_id
480 if len(data) < 4 {
481 return errors.New("invalid delete_id event")
482 }
483 id := binary.LittleEndian.Uint32(data[0:4])
484 d.objects.Delete(id)
485 }
486
487 return nil
488}
489
490// Roundtrip performs a synchronous roundtrip to the compositor
491func (d *Display) Roundtrip() error {
492 log.Printf("wlclient: Roundtrip starting...")
493 // Create callback
494 callbackID := d.allocateID()
495 done := make(chan error, 1)
496
497 log.Printf("wlclient: Registering callback listener for ID %d", callbackID)
498 // Register callback listener
499 d.AddListener(callbackID, 0, func(_ []byte) {
500 log.Printf("wlclient: Callback %d fired", callbackID)
501 d.objects.Delete(callbackID)
502 done <- nil
503 })
504
505 // Send sync request (opcode 0)
506 log.Printf("wlclient: Sending sync request with callback ID %d", callbackID)
507 if err := d.SendRequest(1, 0, callbackID); err != nil {
508 return err
509 }
510
511 // Store the callback object to track it
512 d.objects.Store(callbackID, &callbackObject{
513 BaseProxy: BaseProxy{
514 context: d.Context(),
515 id: callbackID,
516 },
517 display: d,
518 })
519
520 // Process events until callback fires - SIMPLIFIED APPROACH
521 maxIterations := 1000 // Prevent infinite loops
522 for i := 0; i < maxIterations; i++ {
523 // Don't set read deadline for now - causes issues
524 if err := d.Dispatch(); err != nil {
525 log.Printf("wlclient: Dispatch error: %v", err)
526 return err
527 }
528
529 // Check if callback completed
530 select {
531 case err := <-done:
532 log.Printf("wlclient: Roundtrip completed successfully")
533 return err
534 default:
535 // Continue dispatching
536 }
537 }
538
539 return fmt.Errorf("roundtrip failed: max iterations reached")
540}
541
542// AddListener adds an event listener for an object
543func (d *Display) AddListener(objectID uint32, opcode uint16, handler func([]byte)) {
544 // Load or create listener map for object
545 listeners, _ := d.listeners.LoadOrStore(objectID, &sync.Map{})
546 opcodeMap := listeners.(*sync.Map)
547
548 // Load or create handlers slice for opcode
549 handlers, _ := opcodeMap.LoadOrStore(opcode, &[]func([]byte){})
550 handlerSlice := handlers.(*[]func([]byte))
551
552 // Add handler (thread-safe)
553 *handlerSlice = append(*handlerSlice, handler)
554}
555
556// getRegistry gets the global registry
557func (d *Display) getRegistry() error {
558 // Add registry listeners
559 d.AddListener(d.registry.id, 0, d.registry.handleGlobal)
560 d.AddListener(d.registry.id, 1, d.registry.handleGlobalRemove)
561
562 // Send get_registry request (opcode 1)
563 return d.SendRequest(1, 1, d.registry.id)
564}
565
566// Registry returns the global registry
567func (d *Display) Registry() *Registry {
568 return d.registry
569}
570
571// ID returns the registry's object ID
572func (r *Registry) ID() uint32 {
573 return r.id
574}
575
576// handleGlobal handles global announcements
577func (r *Registry) handleGlobal(data []byte) {
578 if len(data) < 8 {
579 // log.Printf("wlclient: handleGlobal: data too short (%d bytes)", len(data))
580 return
581 }
582
583 name := binary.LittleEndian.Uint32(data[0:4])
584 ifaceLen := binary.LittleEndian.Uint32(data[4:8])
585
586 if len(data) < 8+int(ifaceLen)+4 {
587 // log.Printf("wlclient: handleGlobal: insufficient data for interface string (need %d, have %d)", 8+int(ifaceLen)+4, len(data))
588 return
589 }
590
591 // String includes null terminator in length
592 iface := string(data[8 : 8+ifaceLen-1]) // -1 to remove null terminator
593
594 // Calculate padding for 32-bit alignment
595 padding := (4 - (ifaceLen % 4)) % 4
596 versionOffset := 8 + int(ifaceLen) + int(padding)
597
598 if len(data) < versionOffset+4 {
599 // log.Printf("wlclient: handleGlobal: insufficient data for version (need %d, have %d)", versionOffset+4, len(data))
600 return
601 }
602
603 version := binary.LittleEndian.Uint32(data[versionOffset:])
604
605 // log.Printf("wlclient: global announced: %s v%d (name=%d)", iface, version, name)
606
607 // Store global
608 r.mu.Lock()
609 r.globals[name] = Global{
610 Name: name,
611 Interface: iface,
612 Version: version,
613 }
614 r.mu.Unlock()
615
616 // Call specific handler if registered
617 if handler, ok := r.handlers[iface]; ok {
618 // log.Printf("wlclient: calling handler for interface %s", iface)
619 handler(r, name, version)
620 }
621
622 // Call wildcard handler if registered
623 if handler, ok := r.handlers["*"]; ok {
624 // log.Printf("wlclient: calling wildcard handler for interface %s", iface)
625 handler(r, name, version)
626 }
627}
628
629// handleGlobalRemove handles global removal
630func (r *Registry) handleGlobalRemove(data []byte) {
631 if len(data) < 4 {
632 return
633 }
634
635 name := binary.LittleEndian.Uint32(data[0:4])
636
637 r.mu.Lock()
638 delete(r.globals, name)
639 r.mu.Unlock()
640}
641
642// AddHandler adds a handler for a specific interface
643func (r *Registry) AddHandler(iface string, handler GlobalHandler) {
644 r.handlers[iface] = handler
645}
646
647// Bind binds to a global object and returns a typed proxy
648func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy) error {
649 // Set the ID if not already set
650 if proxy.ID() == 0 {
651 newID := r.display.allocateID()
652 log.Printf("wlclient: Allocating new ID %d for interface %s", newID, iface)
653 proxy.SetID(newID)
654 }
655
656 // Ensure proxy has a context set - CRITICAL FIX
657 if proxy.Context() == nil {
658 if baseProxy, ok := proxy.(*BaseProxy); ok {
659 baseProxy.SetContext(r.display.Context())
660 log.Printf("wlclient: Set context for proxy ID %d", proxy.ID())
661 } else if setter, ok := proxy.(interface{ SetContext(*Context) }); ok {
662 setter.SetContext(r.display.Context())
663 log.Printf("wlclient: Set context for proxy ID %d via interface", proxy.ID())
664 } else {
665 return fmt.Errorf("proxy doesn't have context and can't set it")
666 }
667 }
668
669 // Register the proxy
670 proxy.Context().Register(proxy)
671
672 // Also register directly in display objects map
673 r.display.objects.Store(proxy.ID(), proxy)
674 log.Printf("wlclient: Registered proxy ID %d in display objects map", proxy.ID())
675
676 // Send bind request (opcode 0) with proper arguments
677 if err := r.display.SendRequest(r.id, 0, name, iface, version, proxy.ID()); err != nil {
678 proxy.Context().Unregister(proxy)
679 return err
680 }
681
682 return nil
683}
684
685// BindID binds to a global object and returns just the ID (compatibility method)
686func (r *Registry) BindID(name uint32, iface string, version uint32) (uint32, error) {
687 newID := r.display.allocateID()
688
689 // Send bind request (opcode 0) with proper arguments
690 if err := r.display.SendRequest(r.id, 0, name, iface, version, newID); err != nil {
691 return 0, err
692 }
693
694 return newID, nil
695}
696
697// GetGlobals returns all announced globals
698func (r *Registry) GetGlobals() map[uint32]Global {
699 r.mu.RLock()
700 defer r.mu.RUnlock()
701
702 globals := make(map[uint32]Global)
703 for k, v := range r.globals {
704 globals[k] = v
705 }
706 return globals
707}
708
709// FindGlobal finds a global by interface name
710func (r *Registry) FindGlobal(iface string) (Global, bool) {
711 r.mu.RLock()
712 defer r.mu.RUnlock()
713
714 for _, global := range r.globals {
715 if global.Interface == iface {
716 return global, true
717 }
718 }
719 return Global{}, false
720}
721
722// handleServerObject creates and registers server-allocated objects from events
723func (d *Display) handleServerObject(objectID uint32, opcode uint16, body []byte) bool {
724 // Handle specific known protocols that create server objects
725 obj, ok := d.objects.Load(objectID)
726 if !ok {
727 return false
728 }
729
730 // Check if this is a known object type that creates server objects
731 switch obj.(type) {
732 case *Registry:
733 // Registry doesn't create server objects directly
734 return false
735
736 default:
737 // Check if this might be an output manager (interface zwlr_output_manager_v1)
738 if objectID == 5 { // This is likely the output manager based on your logs
739 return d.handleOutputManagerEvent(objectID, opcode, body)
740 }
741 }
742
743 return false
744}
745
746// handleOutputManagerEvent handles events from zwlr_output_manager_v1
747func (d *Display) handleOutputManagerEvent(objectID uint32, opcode uint16, body []byte) bool {
748 switch opcode {
749 case 0: // head event - creates new zwlr_output_head_v1 object
750 if len(body) < 4 {
751 log.Printf("wlclient: head event body too short: %d bytes", len(body))
752 return false
753 }
754
755 // Parse the new_id for the head object
756 headID := binary.LittleEndian.Uint32(body[0:4])
757 log.Printf("wlclient: Creating server object (head) with ID %d", headID)
758
759 // Create a proper OutputHead object
760 headProxy := &OutputHead{
761 BaseProxy: BaseProxy{
762 id: headID,
763 context: d.Context(),
764 },
765 }
766
767 // Register the new head object
768 d.objects.Store(headID, headProxy)
769 log.Printf("wlclient: Registered server-created head object ID %d", headID)
770 return true
771
772 case 1: // done event
773 log.Printf("wlclient: Output manager done event")
774 return false
775
776 case 2: // finished event
777 log.Printf("wlclient: Output manager finished event")
778 return false
779 }
780
781 return false
782}
783
784// OutputHead represents a zwlr_output_head_v1 object
785type OutputHead struct {
786 BaseProxy
787 name string
788 description string
789 width int32
790 height int32
791}
792
793// Dispatch handles head events
794func (h *OutputHead) Dispatch(event *Event) {
795 log.Printf("wlclient: OutputHead.Dispatch ID %d, opcode %d", h.id, event.Opcode)
796 switch event.Opcode {
797 case 0: // name
798 h.name = event.String()
799 log.Printf("wlclient: Head %d name: %s", h.id, h.name)
800 case 1: // description
801 h.description = event.String()
802 log.Printf("wlclient: Head %d description: %s", h.id, h.description)
803 case 2: // physical_size
804 h.width = event.Int32()
805 h.height = event.Int32()
806 log.Printf("wlclient: Head %d physical size: %dx%d mm", h.id, h.width, h.height)
807 case 3: // mode (creates new mode object)
808 modeID := event.Uint32()
809 log.Printf("wlclient: Head %d mode object created: ID %d", h.id, modeID)
810 // Create mode object
811 mode := &OutputMode{
812 BaseProxy: BaseProxy{
813 id: modeID,
814 context: h.context,
815 },
816 }
817 h.context.display.objects.Store(modeID, mode)
818 case 9: // finished
819 log.Printf("wlclient: Head %d finished", h.id)
820 h.context.Unregister(h)
821 default:
822 log.Printf("wlclient: Head %d unhandled opcode %d", h.id, event.Opcode)
823 }
824}
825
826// OutputMode represents a zwlr_output_mode_v1 object
827type OutputMode struct {
828 BaseProxy
829 width int32
830 height int32
831 refresh int32
832}
833
834// Dispatch handles mode events
835func (m *OutputMode) Dispatch(event *Event) {
836 log.Printf("wlclient: OutputMode.Dispatch ID %d, opcode %d", m.id, event.Opcode)
837 switch event.Opcode {
838 case 0: // size
839 m.width = event.Int32()
840 m.height = event.Int32()
841 log.Printf("wlclient: Mode %d size: %dx%d", m.id, m.width, m.height)
842 case 1: // refresh
843 m.refresh = event.Int32()
844 log.Printf("wlclient: Mode %d refresh: %d mHz", m.id, m.refresh)
845 case 2: // preferred
846 log.Printf("wlclient: Mode %d is preferred", m.id)
847 case 3: // finished
848 log.Printf("wlclient: Mode %d finished", m.id)
849 m.context.Unregister(m)
850 default:
851 log.Printf("wlclient: Mode %d unhandled opcode %d", m.id, event.Opcode)
852 }
853}