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 "net"
14 "os"
15 "path/filepath"
16 "sync"
17 "sync/atomic"
18)
19
20// Pre-allocated buffer pool for performance
21var bufferPool = sync.Pool{
22 New: func() interface{} {
23 return &bytes.Buffer{}
24 },
25}
26
27// Fixed represents a 24.8 fixed-point number
28type Fixed int32
29
30// Float64 converts Fixed to float64
31func (f Fixed) Float64() float64 {
32 return float64(f) / 256.0
33}
34
35// NewFixed creates a Fixed from float64
36func NewFixed(v float64) Fixed {
37 return Fixed(v * 256.0)
38}
39
40// Object represents a Wayland object
41type Object interface {
42 ID() uint32
43}
44
45// Display represents a connection to the Wayland display
46type Display struct {
47 conn net.Conn
48 unix *net.UnixConn // set when conn is a Unix socket, nil otherwise
49 objects sync.Map // map[uint32]Object
50 nextID uint32
51 sendMu sync.Mutex
52 recvMu sync.Mutex
53 listeners sync.Map // map[uint32]map[uint16][]func([]byte)
54
55 // High-performance event dispatcher
56 dispatcher *EventDispatcher
57
58 // Core objects
59 registry *Registry
60 context *Context // Store context once
61
62 // Error state
63 lastError error
64 lastErrorCode uint32
65 lastErrorObj uint32
66
67 // Connection-local receive state. rbuf holds bytes read from the socket
68 // that do not yet form a complete frame; recvBuf is the scratch buffer
69 // used for a single read. pendingFDs holds ancillary descriptors that
70 // arrived with those bytes and have not been consumed yet.
71 rbuf []byte
72 recvBuf []byte
73 readErr error
74 maxMsgLen uint32
75 closed atomic.Bool
76 fdMu sync.Mutex
77 pendingFDs []int
78}
79
80// newDisplay builds a Display over an established connection.
81func newDisplay(conn net.Conn) *Display {
82 d := &Display{
83 conn: conn,
84 nextID: 2, // 1 is reserved for wl_display
85 dispatcher: NewEventDispatcher(),
86 }
87 if uc, ok := conn.(*net.UnixConn); ok {
88 d.unix = uc
89 }
90 d.context = NewContext(d)
91 // Register display object (ID 1)
92 d.objects.Store(uint32(displayObjectID), d)
93 // Initialize registry
94 d.registry = &Registry{
95 id: d.allocateID(),
96 display: d,
97 globals: make(map[uint32]Global),
98 handlers: make(map[string]GlobalHandler),
99 }
100 d.objects.Store(d.registry.id, d.registry)
101 return d
102}
103
104// Registry represents the global registry
105type Registry struct {
106 id uint32
107 display *Display
108 globals map[uint32]Global
109 mu sync.RWMutex
110 handlers map[string]GlobalHandler
111}
112
113// callbackObject represents a wl_callback object
114type callbackObject struct {
115 BaseProxy
116 display *Display
117}
118
119func (c *callbackObject) ID() uint32 {
120 return c.id
121}
122
123// Dispatch handles callback events (opcode 0 = done)
124func (c *callbackObject) Dispatch(event *Event) {
125 if event.Opcode != 0 { // done event
126 return
127 }
128 c.display.notifyListeners(c.id, 0, event.data)
129}
130
131// Global represents a global object
132type Global struct {
133 Name uint32
134 Interface string
135 Version uint32
136}
137
138// GlobalHandler is called when a global is announced
139type GlobalHandler func(registry *Registry, name uint32, version uint32)
140
141// Connect connects to the Wayland display and requests the registry.
142func Connect(socketPath string) (*Display, error) {
143 if socketPath == "" {
144 socketPath = os.Getenv("WAYLAND_DISPLAY")
145 if socketPath == "" {
146 socketPath = "wayland-0"
147 }
148 }
149
150 // Resolve socket path
151 if !filepath.IsAbs(socketPath) {
152 runDir := os.Getenv("XDG_RUNTIME_DIR")
153 if runDir == "" {
154 return nil, errors.New("XDG_RUNTIME_DIR not set")
155 }
156 socketPath = filepath.Join(runDir, socketPath)
157 }
158
159 conn, err := net.Dial("unix", socketPath)
160 if err != nil {
161 return nil, fmt.Errorf("failed to connect to Wayland: %w", err)
162 }
163
164 d := newDisplay(conn)
165
166 // Get registry
167 if err := d.getRegistry(); err != nil {
168 _ = d.Close()
169 return nil, fmt.Errorf("failed to get registry: %w", err)
170 }
171
172 // Don't do initial roundtrip here - let the caller do it after setting up handlers
173 return d, nil
174}
175
176// Close closes the display connection. It is safe to call more than once;
177// later calls are no-ops. After Close, Dispatch returns net.ErrClosed.
178func (d *Display) Close() error {
179 if !d.closed.CompareAndSwap(false, true) {
180 return nil
181 }
182 d.closePendingFDs()
183 return d.conn.Close()
184}
185
186// Closed reports whether the connection has been closed.
187func (d *Display) Closed() bool {
188 return d.closed.Load()
189}
190
191// ID returns the display's object ID (always 1)
192func (d *Display) ID() uint32 {
193 return 1
194}
195
196// RegisterEventHandler registers a high-performance event handler
197func (d *Display) RegisterEventHandler(objectID uint32, opcode uint16, handler EventHandler) {
198 d.dispatcher.RegisterHandler(objectID, opcode, handler)
199}
200
201// allocateID allocates a new object ID
202func (d *Display) allocateID() uint32 {
203 return atomic.AddUint32(&d.nextID, 1) - 1
204}
205
206// AllocateID allocates a new object ID (public method)
207func (d *Display) AllocateID() uint32 {
208 return d.allocateID()
209}
210
211// SendRequest sends a request to the compositor
212func (d *Display) SendRequest(objectID uint32, opcode uint16, args ...interface{}) error {
213 return d.SendRequestWithFDs(objectID, opcode, nil, args...)
214}
215
216// SendRequestWithFDs sends a request with file descriptors
217func (d *Display) SendRequestWithFDs(objectID uint32, opcode uint16, fds []int, args ...interface{}) error {
218 // Get buffer from pool
219 buf := bufferPool.Get().(*bytes.Buffer)
220 defer func() {
221 buf.Reset()
222 bufferPool.Put(buf)
223 }()
224
225 // Write header placeholder
226 header := make([]byte, 8)
227 _, _ = buf.Write(header)
228
229 // Marshal arguments
230 for _, arg := range args {
231 if err := d.marshalArg(buf, arg); err != nil {
232 return fmt.Errorf("failed to marshal argument: %w", err)
233 }
234 }
235
236 // Update header with actual size in bytes
237 bufLen := buf.Len()
238 if bufLen > 0xFFFF {
239 return fmt.Errorf("message too large: %d bytes", bufLen)
240 }
241 size := uint32(bufLen) // Safe: checked above
242 binary.LittleEndian.PutUint32(header[0:4], objectID)
243 // Upper 16 bits = size, lower 16 bits = opcode
244 binary.LittleEndian.PutUint32(header[4:8], (size<<16)|uint32(opcode))
245
246 // Update buffer with correct header
247 data := buf.Bytes()
248 copy(data[0:8], header)
249
250 // Send message with optional file descriptors
251 return d.sendmsgWithFDs(data, fds)
252}
253
254// marshalArg marshals a single argument
255func (d *Display) marshalArg(buf *bytes.Buffer, arg interface{}) error {
256 switch v := arg.(type) {
257 case uint32:
258 return binary.Write(buf, binary.LittleEndian, v)
259 case int32:
260 return binary.Write(buf, binary.LittleEndian, v)
261 case Fixed:
262 return binary.Write(buf, binary.LittleEndian, int32(v))
263 case string:
264 // String format: length (including null) + string + null + padding
265 strlen := len(v) + 1
266 if strlen > 0xFFFFFFFF {
267 return fmt.Errorf("string too long: %d bytes", strlen)
268 }
269 if err := binary.Write(buf, binary.LittleEndian, uint32(strlen)); err != nil { // Safe: checked above
270 return err
271 }
272 _, _ = buf.WriteString(v)
273 _ = buf.WriteByte(0)
274 // Pad to 32-bit boundary
275 padding := (4 - (strlen % 4)) % 4
276 for i := 0; i < padding; i++ {
277 _ = buf.WriteByte(0)
278 }
279 case []byte:
280 // Array format: length + data + padding
281 arrlen := len(v)
282 if arrlen > 0xFFFFFFFF {
283 return fmt.Errorf("array too long: %d bytes", arrlen)
284 }
285 if err := binary.Write(buf, binary.LittleEndian, uint32(arrlen)); err != nil {
286 return err
287 }
288 _, _ = buf.Write(v)
289 // Pad to 32-bit boundary
290 padding := (4 - (arrlen % 4)) % 4
291 for i := 0; i < padding; i++ {
292 _ = buf.WriteByte(0)
293 }
294 case Object:
295 if v != nil {
296 return binary.Write(buf, binary.LittleEndian, v.ID())
297 }
298 return binary.Write(buf, binary.LittleEndian, uint32(0))
299 case nil:
300 // Null object
301 return binary.Write(buf, binary.LittleEndian, uint32(0))
302 case int:
303 // File descriptor - write placeholder in message
304 // Actual FD will be sent via SCM_RIGHTS
305 return binary.Write(buf, binary.LittleEndian, uint32(0))
306 case uintptr:
307 // File descriptor as uintptr - NO placeholder in message for neurlang compatibility
308 // FD is ONLY sent via SCM_RIGHTS, not in the message body
309 return nil
310 default:
311 return fmt.Errorf("unsupported argument type: %T", arg)
312 }
313 return nil
314}
315
316// Dispatch reads one complete Wayland message and delivers it to the object
317// that owns it.
318//
319// Read boundaries never define message boundaries: a header split across
320// several reads and several messages arriving in one read are both handled.
321// Dispatch returns net.ErrClosed after Close, a typed ProtocolError for a
322// malformed frame, and io.ErrUnexpectedEOF when the peer closes mid-frame.
323func (d *Display) Dispatch() error {
324 d.recvMu.Lock()
325 defer d.recvMu.Unlock()
326
327 if d.closed.Load() {
328 return net.ErrClosed
329 }
330
331 frame, err := d.nextFrame()
332 if err != nil {
333 return d.fail(err)
334 }
335 return d.fail(d.dispatchFrame(frame))
336}
337
338// fail releases resources that cannot be used once the connection is
339// unusable. A protocol violation invalidates the whole connection, so queued
340// descriptors are closed; transport errors may be transient and keep them.
341func (d *Display) fail(err error) error {
342 if err == nil {
343 return nil
344 }
345 var perr *ProtocolError
346 if errors.As(err, &perr) {
347 d.closePendingFDs()
348 }
349 return err
350}
351
352// maxMessageSize reports the largest accepted message size.
353func (d *Display) maxMessageSize() uint32 {
354 if d.maxMsgLen == 0 {
355 return DefaultMaxMessageSize
356 }
357 return d.maxMsgLen
358}
359
360// nextFrame returns the next complete frame, reading more bytes as needed.
361func (d *Display) nextFrame() (receivedFrame, error) {
362 for {
363 if len(d.rbuf) >= HeaderSize {
364 object, opcode, size, err := parseHeader(d.rbuf[:HeaderSize], d.maxMessageSize())
365 if err != nil {
366 return receivedFrame{}, err
367 }
368 if uint32(len(d.rbuf)) >= size {
369 frame := receivedFrame{object: object, opcode: opcode}
370 if size > HeaderSize {
371 frame.body = make([]byte, size-HeaderSize)
372 copy(frame.body, d.rbuf[HeaderSize:size])
373 }
374 d.consume(size)
375 return frame, nil
376 }
377 }
378 if err := d.fill(); err != nil {
379 return receivedFrame{}, err
380 }
381 }
382}
383
384// consume removes the first n bytes from the receive buffer.
385func (d *Display) consume(n uint32) {
386 rest := copy(d.rbuf, d.rbuf[n:])
387 d.rbuf = d.rbuf[:rest]
388}
389
390// fill reads one chunk from the connection into the receive buffer. Bytes that
391// arrive together with an error are kept so they are still parsed before the
392// error surfaces.
393func (d *Display) fill() error {
394 if d.readErr != nil {
395 return d.readErr
396 }
397 if d.recvBuf == nil {
398 d.recvBuf = make([]byte, readChunkSize)
399 }
400 n, err := d.readChunk()
401 if n > 0 {
402 d.rbuf = append(d.rbuf, d.recvBuf[:n]...)
403 }
404 if err != nil {
405 if errors.Is(err, io.EOF) {
406 err = io.ErrUnexpectedEOF
407 }
408 d.readErr = err
409 // A control-message failure corrupts the descriptor stream, so it is
410 // reported immediately instead of after the buffered bytes are used.
411 var perr *ProtocolError
412 if n == 0 || errors.As(err, &perr) {
413 return err
414 }
415 }
416 return nil
417}
418
419// dispatchFrame delivers one complete frame to the object that owns it.
420func (d *Display) dispatchFrame(f receivedFrame) error {
421 if f.object == displayObjectID {
422 return d.handleDisplayEvent(f.opcode, f.body)
423 }
424
425 obj, ok := d.objects.Load(f.object)
426 if !ok {
427 return &ProtocolError{
428 Kind: "unknown_object",
429 Object: f.object,
430 Opcode: f.opcode,
431 Size: uint32(len(f.body)) + HeaderSize,
432 Err: ErrUnknownObject,
433 }
434 }
435
436 // Server-created objects are announced by an event on a known object.
437 d.handleServerObject(f.object, f.opcode, f.body)
438
439 if proxy, ok := obj.(Proxy); ok && proxy != nil {
440 proxy.Dispatch(&Event{
441 ProxyID: f.object,
442 Opcode: f.opcode,
443 data: f.body,
444 display: d,
445 })
446 return nil
447 }
448
449 // High-performance dispatcher, then compatibility listeners.
450 d.dispatcher.Dispatch(f.object, f.opcode, f.body)
451 d.notifyListeners(f.object, f.opcode, f.body)
452 return nil
453}
454
455// notifyListeners runs the listeners registered for an object and opcode.
456func (d *Display) notifyListeners(objectID uint32, opcode uint16, body []byte) {
457 listeners, ok := d.listeners.Load(objectID)
458 if !ok {
459 return
460 }
461 opcodeMap, ok := listeners.(*sync.Map)
462 if !ok {
463 return
464 }
465 handlers, ok := opcodeMap.Load(opcode)
466 if !ok {
467 return
468 }
469 handlerSlice, ok := handlers.(*[]func([]byte))
470 if !ok || handlerSlice == nil {
471 return
472 }
473 for _, handler := range *handlerSlice {
474 if handler != nil {
475 handler(body)
476 }
477 }
478}
479
480// handleDisplayEvent handles events on the display object
481func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error {
482 switch opcode {
483 case 0: // error
484 if len(data) < 8 {
485 return &ProtocolError{Kind: "short_display_error", Object: displayObjectID, Opcode: opcode, Size: uint32(len(data)) + HeaderSize, Err: ErrMalformedFrame}
486 }
487 event := &Event{ProxyID: displayObjectID, Opcode: opcode, data: data, display: d}
488 objectID := event.Uint32()
489 code := event.Uint32()
490 message := event.String()
491
492 d.lastErrorCode = code
493 d.lastErrorObj = objectID
494 d.lastError = &DisplayError{ObjectID: objectID, Code: code, Message: message}
495 return d.lastError
496
497 case 1: // delete_id
498 if len(data) < 4 {
499 return &ProtocolError{Kind: "short_delete_id", Object: displayObjectID, Opcode: opcode, Size: uint32(len(data)) + HeaderSize, Err: ErrMalformedFrame}
500 }
501 id := binary.LittleEndian.Uint32(data[0:4])
502 d.objects.Delete(id)
503 return nil
504
505 default:
506 return &ProtocolError{Kind: "unknown_opcode", Object: displayObjectID, Opcode: opcode, Err: ErrUnknownOpcode}
507 }
508}
509
510// Roundtrip performs a synchronous roundtrip to the compositor
511func (d *Display) Roundtrip() error {
512 callbackID := d.allocateID()
513 done := make(chan error, 1)
514
515 d.AddListener(callbackID, 0, func(_ []byte) {
516 d.objects.Delete(callbackID)
517 done <- nil
518 })
519
520 // Send sync request (opcode 0)
521 if err := d.SendRequest(displayObjectID, 0, callbackID); err != nil {
522 return err
523 }
524
525 d.objects.Store(callbackID, &callbackObject{
526 BaseProxy: BaseProxy{
527 context: d.Context(),
528 id: callbackID,
529 },
530 display: d,
531 })
532
533 for i := 0; i < 1000; i++ {
534 if err := d.Dispatch(); err != nil {
535 return err
536 }
537 select {
538 case err := <-done:
539 return err
540 default:
541 }
542 }
543
544 return fmt.Errorf("roundtrip failed: max iterations reached")
545}
546
547// AddListener adds an event listener for an object
548func (d *Display) AddListener(objectID uint32, opcode uint16, handler func([]byte)) {
549 // Load or create listener map for object
550 listeners, _ := d.listeners.LoadOrStore(objectID, &sync.Map{})
551 opcodeMap := listeners.(*sync.Map)
552
553 // Load or create handlers slice for opcode
554 handlers, _ := opcodeMap.LoadOrStore(opcode, &[]func([]byte){})
555 handlerSlice := handlers.(*[]func([]byte))
556
557 // Add handler (thread-safe)
558 *handlerSlice = append(*handlerSlice, handler)
559}
560
561// getRegistry gets the global registry
562func (d *Display) getRegistry() error {
563 // Add registry listeners
564 d.AddListener(d.registry.id, 0, d.registry.handleGlobal)
565 d.AddListener(d.registry.id, 1, d.registry.handleGlobalRemove)
566
567 // Send get_registry request (opcode 1)
568 return d.SendRequest(1, 1, d.registry.id)
569}
570
571// Registry returns the global registry
572func (d *Display) Registry() *Registry {
573 return d.registry
574}
575
576// ID returns the registry's object ID
577func (r *Registry) ID() uint32 {
578 return r.id
579}
580
581// handleGlobal handles global announcements
582func (r *Registry) handleGlobal(data []byte) {
583 if len(data) < 8 {
584 // log.Printf("wlclient: handleGlobal: data too short (%d bytes)", len(data))
585 return
586 }
587
588 name := binary.LittleEndian.Uint32(data[0:4])
589 ifaceLen := binary.LittleEndian.Uint32(data[4:8])
590
591 if len(data) < 8+int(ifaceLen)+4 {
592 // log.Printf("wlclient: handleGlobal: insufficient data for interface string (need %d, have %d)", 8+int(ifaceLen)+4, len(data))
593 return
594 }
595
596 // String includes null terminator in length
597 iface := string(data[8 : 8+ifaceLen-1]) // -1 to remove null terminator
598
599 // Calculate padding for 32-bit alignment
600 padding := (4 - (ifaceLen % 4)) % 4
601 versionOffset := 8 + int(ifaceLen) + int(padding)
602
603 if len(data) < versionOffset+4 {
604 // log.Printf("wlclient: handleGlobal: insufficient data for version (need %d, have %d)", versionOffset+4, len(data))
605 return
606 }
607
608 version := binary.LittleEndian.Uint32(data[versionOffset:])
609
610 // log.Printf("wlclient: global announced: %s v%d (name=%d)", iface, version, name)
611
612 // Store global
613 r.mu.Lock()
614 r.globals[name] = Global{
615 Name: name,
616 Interface: iface,
617 Version: version,
618 }
619 r.mu.Unlock()
620
621 // Call specific handler if registered
622 if handler, ok := r.handlers[iface]; ok {
623 // log.Printf("wlclient: calling handler for interface %s", iface)
624 handler(r, name, version)
625 }
626
627 // Call wildcard handler if registered
628 if handler, ok := r.handlers["*"]; ok {
629 // log.Printf("wlclient: calling wildcard handler for interface %s", iface)
630 handler(r, name, version)
631 }
632}
633
634// handleGlobalRemove handles global removal
635func (r *Registry) handleGlobalRemove(data []byte) {
636 if len(data) < 4 {
637 return
638 }
639
640 name := binary.LittleEndian.Uint32(data[0:4])
641
642 r.mu.Lock()
643 delete(r.globals, name)
644 r.mu.Unlock()
645}
646
647// AddHandler adds a handler for a specific interface
648func (r *Registry) AddHandler(iface string, handler GlobalHandler) {
649 r.handlers[iface] = handler
650}
651
652// Bind binds to a global object and returns a typed proxy
653func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy) error {
654 // Set the ID if not already set
655 if proxy.ID() == 0 {
656 proxy.SetID(r.display.allocateID())
657 }
658
659 // Ensure proxy has a context set - CRITICAL FIX
660 if proxy.Context() == nil {
661 if baseProxy, ok := proxy.(*BaseProxy); ok {
662 baseProxy.SetContext(r.display.Context())
663 } else if setter, ok := proxy.(interface{ SetContext(*Context) }); ok {
664 setter.SetContext(r.display.Context())
665 } else {
666 return fmt.Errorf("proxy doesn't have context and can't set it")
667 }
668 }
669
670 // Register the proxy
671 proxy.Context().Register(proxy)
672
673 // Also register directly in display objects map
674 r.display.objects.Store(proxy.ID(), proxy)
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 return false
752 }
753
754 // Parse the new_id for the head object
755 headID := binary.LittleEndian.Uint32(body[0:4])
756
757 // Create a proper OutputHead object
758 headProxy := &OutputHead{
759 BaseProxy: BaseProxy{
760 id: headID,
761 context: d.Context(),
762 },
763 }
764
765 // Register the new head object
766 d.objects.Store(headID, headProxy)
767 return true
768
769 case 1: // done event
770 return false
771
772 case 2: // finished event
773 return false
774 }
775
776 return false
777}
778
779// OutputHead represents a zwlr_output_head_v1 object
780type OutputHead struct {
781 BaseProxy
782 name string
783 description string
784 width int32
785 height int32
786}
787
788// Dispatch handles head events
789func (h *OutputHead) Dispatch(event *Event) {
790 switch event.Opcode {
791 case 0: // name
792 h.name = event.String()
793 case 1: // description
794 h.description = event.String()
795 case 2: // physical_size
796 h.width = event.Int32()
797 h.height = event.Int32()
798 case 3: // mode (creates new mode object)
799 modeID := event.Uint32()
800 // Create mode object
801 mode := &OutputMode{
802 BaseProxy: BaseProxy{
803 id: modeID,
804 context: h.context,
805 },
806 }
807 h.context.display.objects.Store(modeID, mode)
808 case 9: // finished
809 h.context.Unregister(h)
810 default:
811 }
812}
813
814// OutputMode represents a zwlr_output_mode_v1 object
815type OutputMode struct {
816 BaseProxy
817 width int32
818 height int32
819 refresh int32
820}
821
822// Dispatch handles mode events
823func (m *OutputMode) Dispatch(event *Event) {
824 switch event.Opcode {
825 case 0: // size
826 m.width = event.Int32()
827 m.height = event.Int32()
828 case 1: // refresh
829 m.refresh = event.Int32()
830 case 2: // preferred
831 case 3: // finished
832 m.context.Unregister(m)
833 default:
834 }
835}