detached c83f653

merge: generated core bindings and hardened transport (Veya P2)

commit
c83f653555760c2695fdf83ac68e470d4d385fb0
author
brice · · 2026-09-28 07:48 UTC
merge of
ab733e5 + 1cc5bed · diff against the first parent
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
24 files · +10254 -762
A benchmark_test.go +26 -0
@@ -0,0 +1,26 @@
1+package wlturbo
2+
3+import "testing"
4+
5+// BenchmarkWireDispatch isolates framed typed dispatch from socket scheduling.
6+func BenchmarkWireDispatch(b *testing.B) {
7+ d := newDisplay(&chunkConn{})
8+ p := &benchProxy{BaseProxy: BaseProxy{id: 7, context: d.context}}
9+ d.context.Register(p)
10+ f := receivedFrame{object: 7, opcode: 0, body: []byte{1, 0, 0, 0}}
11+ b.ReportAllocs()
12+ b.ResetTimer()
13+ for i := 0; i < b.N; i++ {
14+ if err := d.dispatchFrame(f); err != nil {
15+ b.Fatal(err)
16+ }
17+ }
18+}
19+
20+type benchProxy struct {
21+ BaseProxy
22+ value uint32
23+}
24+
25+func (p *benchProxy) EventSignature(op uint16) (string, bool) { return "uint,", op == 0 }
26+func (p *benchProxy) Dispatch(e *Event) { p.value = e.Uint32() }
M client.go +259 -228
@@ -13,6 +13,7 @@ import (
1313 "net"
1414 "os"
1515 "path/filepath"
16+ "strings"
1617 "sync"
1718 "sync/atomic"
1819 )
@@ -44,13 +45,19 @@ type Object interface {
4445
4546 // Display represents a connection to the Wayland display
4647 type 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)
48+ conn net.Conn
49+ unix *net.UnixConn // set when conn is a Unix socket, nil otherwise
50+ objects sync.Map // map[uint32]Object
51+ nextID uint32
52+ idMu sync.Mutex
53+ freeIDs []uint32
54+ retiredIDs map[uint32]bool
55+ sendMu sync.Mutex
56+ recvMu sync.Mutex
57+ dispatchMu sync.Mutex // serialize readers without holding receive state across handlers
58+ signatures sync.Map
59+ listenerMu sync.Mutex
60+ listeners sync.Map // map[uint32]map[uint16][]func([]byte)
5461
5562 // High-performance event dispatcher
5663 dispatcher *EventDispatcher
@@ -73,7 +80,6 @@ type Display struct {
7380 readErr error
7481 maxMsgLen uint32
7582 closed atomic.Bool
76- fdMu sync.Mutex
7783 pendingFDs []int
7884 }
7985
@@ -103,11 +109,12 @@ func newDisplay(conn net.Conn) *Display {
103109
104110 // Registry represents the global registry
105111 type Registry struct {
106- id uint32
107- display *Display
108- globals map[uint32]Global
109- mu sync.RWMutex
110- handlers map[string]GlobalHandler
112+ id uint32
113+ display *Display
114+ globals map[uint32]Global
115+ mu sync.RWMutex
116+ handlers map[string]GlobalHandler
117+ removeHandlers []RegistryGlobalRemoveHandler
111118 }
112119
113120 // callbackObject represents a wl_callback object
@@ -119,6 +126,12 @@ type callbackObject struct {
119126 func (c *callbackObject) ID() uint32 {
120127 return c.id
121128 }
129+func (c *callbackObject) EventSignature(op uint16) (string, bool) {
130+ if op == 0 {
131+ return "uint,", true
132+ }
133+ return "", false
134+}
122135
123136 // Dispatch handles callback events (opcode 0 = done)
124137 func (c *callbackObject) Dispatch(event *Event) {
@@ -126,6 +139,7 @@ func (c *callbackObject) Dispatch(event *Event) {
126139 return
127140 }
128141 c.display.notifyListeners(c.id, 0, event.data)
142+ c.display.context.Unregister(c)
129143 }
130144
131145 // Global represents a global object
@@ -138,6 +152,16 @@ type Global struct {
138152 // GlobalHandler is called when a global is announced
139153 type GlobalHandler func(registry *Registry, name uint32, version uint32)
140154
155+// ConnectFromConn attaches to an established Wayland socket and requests its registry.
156+func ConnectFromConn(conn net.Conn) (*Display, error) {
157+ d := newDisplay(conn)
158+ if err := d.getRegistry(); err != nil {
159+ _ = d.Close()
160+ return nil, fmt.Errorf("get registry: %w", err)
161+ }
162+ return d, nil
163+}
164+
141165 // Connect connects to the Wayland display and requests the registry.
142166 func Connect(socketPath string) (*Display, error) {
143167 if socketPath == "" {
@@ -161,16 +185,7 @@ func Connect(socketPath string) (*Display, error) {
161185 return nil, fmt.Errorf("failed to connect to Wayland: %w", err)
162186 }
163187
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
188+ return ConnectFromConn(conn)
174189 }
175190
176191 // Close closes the display connection. It is safe to call more than once;
@@ -179,8 +194,12 @@ func (d *Display) Close() error {
179194 if !d.closed.CompareAndSwap(false, true) {
180195 return nil
181196 }
197+ d.context.closed.Store(true)
198+ err := d.conn.Close()
199+ d.recvMu.Lock()
182200 d.closePendingFDs()
183- return d.conn.Close()
201+ d.recvMu.Unlock()
202+ return err
184203 }
185204
186205 // Closed reports whether the connection has been closed.
@@ -200,7 +219,20 @@ func (d *Display) RegisterEventHandler(objectID uint32, opcode uint16, handler E
200219
201220 // allocateID allocates a new object ID
202221 func (d *Display) allocateID() uint32 {
203- return atomic.AddUint32(&d.nextID, 1) - 1
222+ d.idMu.Lock()
223+ defer d.idMu.Unlock()
224+ if n := len(d.freeIDs); n != 0 {
225+ id := d.freeIDs[n-1]
226+ d.freeIDs = d.freeIDs[:n-1]
227+ delete(d.retiredIDs, id)
228+ return id
229+ }
230+ if d.nextID == 0 || d.nextID >= 0xff000000 {
231+ return 0 // exhausted; never allocate a reserved server ID
232+ }
233+ id := d.nextID
234+ d.nextID++
235+ return id
204236 }
205237
206238 // AllocateID allocates a new object ID (public method)
@@ -215,6 +247,12 @@ func (d *Display) SendRequest(objectID uint32, opcode uint16, args ...interface{
215247
216248 // SendRequestWithFDs sends a request with file descriptors
217249 func (d *Display) SendRequestWithFDs(objectID uint32, opcode uint16, fds []int, args ...interface{}) error {
250+ return d.sendRequest(objectID, opcode, fds, nil, args)
251+}
252+
253+// sendRequest marshals a request, then runs guard and writes the message
254+// under the send lock. Guard failures and marshaling errors write nothing.
255+func (d *Display) sendRequest(objectID uint32, opcode uint16, fds []int, guard func() error, args []interface{}) error {
218256 // Get buffer from pool
219257 buf := bufferPool.Get().(*bytes.Buffer)
220258 defer func() {
@@ -235,8 +273,8 @@ func (d *Display) SendRequestWithFDs(objectID uint32, opcode uint16, fds []int,
235273
236274 // Update header with actual size in bytes
237275 bufLen := buf.Len()
238- if bufLen > 0xFFFF {
239- return fmt.Errorf("message too large: %d bytes", bufLen)
276+ if bufLen > 0xFFFF || bufLen%4 != 0 {
277+ return fmt.Errorf("invalid message size: %d bytes", bufLen)
240278 }
241279 size := uint32(bufLen) // Safe: checked above
242280 binary.LittleEndian.PutUint32(header[0:4], objectID)
@@ -248,7 +286,7 @@ func (d *Display) SendRequestWithFDs(objectID uint32, opcode uint16, fds []int,
248286 copy(data[0:8], header)
249287
250288 // Send message with optional file descriptors
251- return d.sendmsgWithFDs(data, fds)
289+ return d.sendmsgWithFDs(data, fds, guard)
252290 }
253291
254292 // marshalArg marshals a single argument
@@ -263,7 +301,7 @@ func (d *Display) marshalArg(buf *bytes.Buffer, arg interface{}) error {
263301 case string:
264302 // String format: length (including null) + string + null + padding
265303 strlen := len(v) + 1
266- if strlen > 0xFFFFFFFF {
304+ if strlen > 0xffff-HeaderSize {
267305 return fmt.Errorf("string too long: %d bytes", strlen)
268306 }
269307 if err := binary.Write(buf, binary.LittleEndian, uint32(strlen)); err != nil { // Safe: checked above
@@ -279,7 +317,7 @@ func (d *Display) marshalArg(buf *bytes.Buffer, arg interface{}) error {
279317 case []byte:
280318 // Array format: length + data + padding
281319 arrlen := len(v)
282- if arrlen > 0xFFFFFFFF {
320+ if arrlen > 0xffff-HeaderSize {
283321 return fmt.Errorf("array too long: %d bytes", arrlen)
284322 }
285323 if err := binary.Write(buf, binary.LittleEndian, uint32(arrlen)); err != nil {
@@ -321,14 +359,20 @@ func (d *Display) marshalArg(buf *bytes.Buffer, arg interface{}) error {
321359 // Dispatch returns net.ErrClosed after Close, a typed ProtocolError for a
322360 // malformed frame, and io.ErrUnexpectedEOF when the peer closes mid-frame.
323361 func (d *Display) Dispatch() error {
362+ d.dispatchMu.Lock()
363+ defer d.dispatchMu.Unlock()
324364 d.recvMu.Lock()
325- defer d.recvMu.Unlock()
326365
327366 if d.closed.Load() {
367+ d.recvMu.Unlock()
328368 return net.ErrClosed
329369 }
330370
331371 frame, err := d.nextFrame()
372+ d.recvMu.Unlock()
373+ if d.closed.Load() {
374+ return net.ErrClosed
375+ }
332376 if err != nil {
333377 return d.fail(err)
334378 }
@@ -342,10 +386,15 @@ func (d *Display) fail(err error) error {
342386 if err == nil {
343387 return nil
344388 }
389+ d.recvMu.Lock()
390+ defer d.recvMu.Unlock()
345391 var perr *ProtocolError
346392 if errors.As(err, &perr) {
347393 d.closePendingFDs()
348394 }
395+ if errors.Is(err, io.ErrUnexpectedEOF) {
396+ d.closePendingFDs()
397+ }
349398 return err
350399 }
351400
@@ -376,6 +425,9 @@ func (d *Display) nextFrame() (receivedFrame, error) {
376425 }
377426 }
378427 if err := d.fill(); err != nil {
428+ if d.closed.Load() {
429+ return receivedFrame{}, net.ErrClosed
430+ }
379431 return receivedFrame{}, err
380432 }
381433 }
@@ -416,40 +468,129 @@ func (d *Display) fill() error {
416468 return nil
417469 }
418470
419-// dispatchFrame delivers one complete frame to the object that owns it.
471+// signatureProvider is implemented by generated objects; every opcode must
472+// have a known signature before any descriptor can be assigned or dispatched.
473+type signatureProvider interface{ EventSignature(uint16) (string, bool) }
474+
475+// dispatchFrame delivers one complete frame with precisely its signature's FDs.
420476 func (d *Display) dispatchFrame(f receivedFrame) error {
421- if f.object == displayObjectID {
477+ d.recvMu.Lock()
478+ obj, ev, kind, err := d.prepareFrame(f)
479+ d.recvMu.Unlock()
480+ if err != nil {
481+ return err
482+ }
483+ switch kind {
484+ case 1:
422485 return d.handleDisplayEvent(f.opcode, f.body)
486+ case 2:
487+ d.notifyListeners(f.object, f.opcode, f.body)
488+ return nil
489+ }
490+ defer func() {
491+ for _, fd := range ev.fds {
492+ _ = fd.Close()
493+ }
494+ *ev = Event{}
495+ eventPool.Put(ev)
496+ }()
497+ if proxy, ok := obj.(Proxy); ok {
498+ proxy.Dispatch(ev)
499+ } else {
500+ d.dispatcher.Dispatch(f.object, f.opcode, f.body)
501+ d.notifyListeners(f.object, f.opcode, f.body)
423502 }
503+ return nil
504+}
424505
506+// prepareFrame assigns owned descriptors under recvMu, but never calls user code.
507+func (d *Display) prepareFrame(f receivedFrame) (Object, *Event, uint8, error) {
508+ if d.closed.Load() {
509+ return nil, nil, 0, net.ErrClosed
510+ }
511+ if f.object == displayObjectID {
512+ if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 {
513+ return nil, nil, 0, &ProtocolError{Kind: "extra_fd", Err: ErrMalformedFrame}
514+ }
515+ return nil, nil, 1, nil
516+ }
517+ if f.object == d.registry.id {
518+ var sig string
519+ switch f.opcode {
520+ case 0:
521+ sig = "uint,string,uint,"
522+ case 1:
523+ sig = "uint,"
524+ default:
525+ return nil, nil, 0, &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode}
526+ }
527+ if err := validateEventBody(sig, f.body); err != nil {
528+ return nil, nil, 0, &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err}
529+ }
530+ if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 {
531+ return nil, nil, 0, &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame}
532+ }
533+ return nil, nil, 2, nil
534+ }
425535 obj, ok := d.objects.Load(f.object)
426536 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,
537+ return nil, nil, 0, &ProtocolError{Kind: "unknown_object", Object: f.object, Opcode: f.opcode, Err: ErrUnknownObject}
538+ }
539+ sig, valid := "", false
540+ p, isGenerated := obj.(signatureProvider)
541+ if isGenerated {
542+ sig, valid = p.EventSignature(f.opcode)
543+ }
544+ // Legacy custom proxies may declare their signatures with RegisterEventSignature.
545+ if !valid && !isGenerated {
546+ if v, ok := d.signatures.Load(signatureKey{f.object, f.opcode}); ok {
547+ sig, valid = v.(string), true
548+ }
549+ }
550+ if !valid {
551+ return nil, nil, 0, &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode}
552+ }
553+ if err := validateEventBody(sig, f.body); err != nil {
554+ return nil, nil, 0, &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err}
555+ }
556+ count := strings.Count(sig, "fd,")
557+ if len(d.pendingFDs) < count {
558+ return nil, nil, 0, &ProtocolError{Kind: "missing_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame}
559+ }
560+ ev := eventPool.Get().(*Event)
561+ *ev = Event{ProxyID: f.object, Opcode: f.opcode, data: f.body, display: d}
562+ if count != 0 {
563+ ev.fds = make([]*OwnedFD, count)
564+ for i := range ev.fds {
565+ ev.fds[i] = &OwnedFD{fd: d.pendingFDs[i]}
566+ }
567+ d.pendingFDs = d.pendingFDs[count:]
568+ }
569+ // FDs from a single recvmsg can accompany multiple coalesced frames. Once
570+ // all buffered bytes have been framed, surplus descriptors are not legal.
571+ if len(d.rbuf) == 0 && len(d.pendingFDs) != 0 {
572+ for _, fd := range ev.fds {
573+ _ = fd.Close()
433574 }
575+ *ev = Event{}
576+ eventPool.Put(ev)
577+ return nil, nil, 0, &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame}
434578 }
579+ return obj.(Object), ev, 0, nil
580+}
435581
436- // Server-created objects are announced by an event on a known object.
437- d.handleServerObject(f.object, f.opcode, f.body)
582+type signatureKey struct {
583+ object uint32
584+ opcode uint16
585+}
438586
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
587+// RegisterEventSignature defines a signature for a legacy proxy. Generated
588+// proxies supply it themselves and cannot be overridden.
589+func (d *Display) RegisterEventSignature(object uint32, opcode uint16, signature string) {
590+ if object == 0 || object == displayObjectID {
591+ return
447592 }
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
593+ d.signatures.Store(signatureKey{object, opcode}, signature)
453594 }
454595
455596 // notifyListeners runs the listeners registered for an object and opcode.
@@ -466,11 +607,7 @@ func (d *Display) notifyListeners(objectID uint32, opcode uint16, body []byte) {
466607 if !ok {
467608 return
468609 }
469- handlerSlice, ok := handlers.(*[]func([]byte))
470- if !ok || handlerSlice == nil {
471- return
472- }
473- for _, handler := range *handlerSlice {
610+ for _, handler := range handlers.([]func([]byte)) {
474611 if handler != nil {
475612 handler(body)
476613 }
@@ -481,8 +618,8 @@ func (d *Display) notifyListeners(objectID uint32, opcode uint16, body []byte) {
481618 func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error {
482619 switch opcode {
483620 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}
621+ if err := validateEventBody("object,uint,string,", data); err != nil {
622+ return &ProtocolError{Kind: "malformed_display_error", Err: err}
486623 }
487624 event := &Event{ProxyID: displayObjectID, Opcode: opcode, data: data, display: d}
488625 objectID := event.Uint32()
@@ -495,11 +632,22 @@ func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error {
495632 return d.lastError
496633
497634 case 1: // delete_id
498- if len(data) < 4 {
635+ if len(data) != 4 {
499636 return &ProtocolError{Kind: "short_delete_id", Object: displayObjectID, Opcode: opcode, Size: uint32(len(data)) + HeaderSize, Err: ErrMalformedFrame}
500637 }
501638 id := binary.LittleEndian.Uint32(data[0:4])
502- d.objects.Delete(id)
639+ d.idMu.Lock()
640+ _, live := d.objects.Load(id)
641+ if id < 2 || id >= 0xff000000 || id >= d.nextID || live || d.retiredIDs[id] {
642+ d.idMu.Unlock()
643+ return &ProtocolError{Kind: "invalid_delete_id", Object: id, Err: ErrMalformedFrame}
644+ }
645+ if d.retiredIDs == nil {
646+ d.retiredIDs = make(map[uint32]bool)
647+ }
648+ d.retiredIDs[id] = true
649+ d.freeIDs = append(d.freeIDs, id)
650+ d.idMu.Unlock()
503651 return nil
504652
505653 default:
@@ -507,55 +655,42 @@ func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error {
507655 }
508656 }
509657
510-// Roundtrip performs a synchronous roundtrip to the compositor
658+// Roundtrip waits for one wl_display.sync callback. Concurrent event loops
659+// must not call Roundtrip while another goroutine dispatches this connection.
511660 func (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 {
661+ callback, err := d.Sync()
662+ if err != nil {
522663 return err
523664 }
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 {
665+ for {
666+ if err = d.Dispatch(); err != nil {
535667 return err
536668 }
537- select {
538- case err := <-done:
539- return err
540- default:
669+ if _, exists := d.objects.Load(callback.ID()); !exists {
670+ return nil
541671 }
542672 }
543-
544- return fmt.Errorf("roundtrip failed: max iterations reached")
545673 }
546674
547675 // AddListener adds an event listener for an object
548676 func (d *Display) AddListener(objectID uint32, opcode uint16, handler func([]byte)) {
677+ if handler == nil {
678+ return
679+ }
549680 // Load or create listener map for object
550681 listeners, _ := d.listeners.LoadOrStore(objectID, &sync.Map{})
551682 opcodeMap := listeners.(*sync.Map)
552683
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)
684+ d.listenerMu.Lock()
685+ defer d.listenerMu.Unlock()
686+ var handlers []func([]byte)
687+ if old, ok := opcodeMap.Load(opcode); ok {
688+ handlers = old.([]func([]byte))
689+ }
690+ next := make([]func([]byte), len(handlers)+1)
691+ copy(next, handlers)
692+ next[len(handlers)] = handler
693+ opcodeMap.Store(opcode, next)
559694 }
560695
561696 // getRegistry gets the global registry
@@ -581,15 +716,13 @@ func (r *Registry) ID() uint32 {
581716 // handleGlobal handles global announcements
582717 func (r *Registry) handleGlobal(data []byte) {
583718 if len(data) < 8 {
584- // log.Printf("wlclient: handleGlobal: data too short (%d bytes)", len(data))
585719 return
586720 }
587721
588722 name := binary.LittleEndian.Uint32(data[0:4])
589723 ifaceLen := binary.LittleEndian.Uint32(data[4:8])
590724
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))
725+ if ifaceLen == 0 || uint64(ifaceLen)+12 > uint64(len(data)) || data[8+ifaceLen-1] != 0 {
593726 return
594727 }
595728
@@ -600,15 +733,12 @@ func (r *Registry) handleGlobal(data []byte) {
600733 padding := (4 - (ifaceLen % 4)) % 4
601734 versionOffset := 8 + int(ifaceLen) + int(padding)
602735
603- if len(data) < versionOffset+4 {
604- // log.Printf("wlclient: handleGlobal: insufficient data for version (need %d, have %d)", versionOffset+4, len(data))
736+ if len(data) != versionOffset+4 {
605737 return
606738 }
607739
608740 version := binary.LittleEndian.Uint32(data[versionOffset:])
609741
610- // log.Printf("wlclient: global announced: %s v%d (name=%d)", iface, version, name)
611-
612742 // Store global
613743 r.mu.Lock()
614744 r.globals[name] = Global{
@@ -619,15 +749,19 @@ func (r *Registry) handleGlobal(data []byte) {
619749 r.mu.Unlock()
620750
621751 // Call specific handler if registered
622- if handler, ok := r.handlers[iface]; ok {
623- // log.Printf("wlclient: calling handler for interface %s", iface)
752+ r.mu.RLock()
753+ handler, specific := r.handlers[iface]
754+ wildcard, all := r.handlers["*"]
755+ r.mu.RUnlock()
756+ if specific {
757+
624758 handler(r, name, version)
625759 }
626760
627761 // 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)
762+ if all {
763+
764+ wildcard(r, name, version)
631765 }
632766 }
633767
@@ -641,11 +775,17 @@ func (r *Registry) handleGlobalRemove(data []byte) {
641775
642776 r.mu.Lock()
643777 delete(r.globals, name)
778+ handlers := append([]RegistryGlobalRemoveHandler(nil), r.removeHandlers...)
644779 r.mu.Unlock()
780+ for _, handler := range handlers {
781+ handler.HandleRegistryGlobalRemove(RegistryGlobalRemoveEvent{Registry: r, Name: name})
782+ }
645783 }
646784
647785 // AddHandler adds a handler for a specific interface
648786 func (r *Registry) AddHandler(iface string, handler GlobalHandler) {
787+ r.mu.Lock()
788+ defer r.mu.Unlock()
649789 r.handlers[iface] = handler
650790 }
651791
@@ -656,7 +796,7 @@ func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy)
656796 proxy.SetID(r.display.allocateID())
657797 }
658798
659- // Ensure proxy has a context set - CRITICAL FIX
799+ // Bootstrap a generated proxy with this display's context.
660800 if proxy.Context() == nil {
661801 if baseProxy, ok := proxy.(*BaseProxy); ok {
662802 baseProxy.SetContext(r.display.Context())
@@ -667,12 +807,18 @@ func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy)
667807 }
668808 }
669809
810+ if proxy.Context() != r.display.Context() {
811+ return fmt.Errorf("proxy belongs to another display")
812+ }
813+ if proxy.ID() == displayObjectID || proxy.ID() == r.id {
814+ return fmt.Errorf("reserved object ID")
815+ }
816+ if old, exists := r.display.objects.Load(proxy.ID()); exists && old != proxy {
817+ return fmt.Errorf("object ID %d already registered", proxy.ID())
818+ }
670819 // Register the proxy
671820 proxy.Context().Register(proxy)
672821
673- // Also register directly in display objects map
674- r.display.objects.Store(proxy.ID(), proxy)
675-
676822 // Send bind request (opcode 0) with proper arguments
677823 if err := r.display.SendRequest(r.id, 0, name, iface, version, proxy.ID()); err != nil {
678824 proxy.Context().Unregister(proxy)
@@ -718,118 +864,3 @@ func (r *Registry) FindGlobal(iface string) (Global, bool) {
718864 }
719865 return Global{}, false
720866 }
721-
722-// handleServerObject creates and registers server-allocated objects from events
723-func (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
747-func (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
780-type OutputHead struct {
781- BaseProxy
782- name string
783- description string
784- width int32
785- height int32
786-}
787-
788-// Dispatch handles head events
789-func (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
815-type OutputMode struct {
816- BaseProxy
817- width int32
818- height int32
819- refresh int32
820-}
821-
822-// Dispatch handles mode events
823-func (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-}
A cmd/wlturbo-scanner/main.go +132 -0
@@ -0,0 +1,132 @@
1+// Command wlturbo-scanner generates Go bindings from Wayland protocol XML files.
2+//
3+// Usage:
4+//
5+// wlturbo-scanner [flags] protocol.xml
6+//
7+// Flags:
8+//
9+// -o, --output Output file path (default: <protocol>_generated.go)
10+// -p, --package Go package name (default: derived from protocol name)
11+// -h, --help Show help
12+//
13+// Examples:
14+//
15+// # Generate bindings for virtual pointer protocol
16+// wlturbo-scanner -o virtual_pointer.go ../wlr-protocols/unstable/wlr-virtual-pointer-unstable-v1.xml
17+//
18+// # Generate with custom package name
19+// wlturbo-scanner -p vpointer -o vpointer.go protocol.xml
20+package main
21+
22+import (
23+ "flag"
24+ "fmt"
25+ "log"
26+ "os"
27+ "path/filepath"
28+ "strings"
29+
30+ "github.com/bnema/wlturbo/internal/scanner"
31+)
32+
33+func main() {
34+ var (
35+ outputPath string
36+ packageName string
37+ showHelp bool
38+ crossImports crossFlags
39+ )
40+
41+ flag.StringVar(&outputPath, "o", "", "Output file path")
42+ flag.StringVar(&outputPath, "output", "", "Output file path")
43+ flag.StringVar(&packageName, "p", "", "Go package name")
44+ flag.StringVar(&packageName, "package", "", "Go package name")
45+ flag.Var(&crossImports, "import", "Interface=Go import path (repeatable)")
46+ flag.BoolVar(&showHelp, "h", false, "Show help")
47+ flag.BoolVar(&showHelp, "help", false, "Show help")
48+
49+ flag.Usage = func() {
50+ fmt.Fprintf(os.Stderr, "Usage: %s [flags] protocol.xml\n\n", os.Args[0])
51+ fmt.Fprintf(os.Stderr, "Generates Go bindings from Wayland protocol XML files.\n\n")
52+ fmt.Fprintf(os.Stderr, "Flags:\n")
53+ flag.PrintDefaults()
54+ fmt.Fprintf(os.Stderr, "\nExamples:\n")
55+ fmt.Fprintf(os.Stderr, " # Generate bindings for virtual pointer protocol\n")
56+ fmt.Fprintf(os.Stderr, " %s -o virtual_pointer.go wlr-virtual-pointer-unstable-v1.xml\n\n", os.Args[0])
57+ fmt.Fprintf(os.Stderr, " # Generate with custom package name\n")
58+ fmt.Fprintf(os.Stderr, " %s -p vpointer -o vpointer.go protocol.xml\n", os.Args[0])
59+ }
60+
61+ flag.Parse()
62+
63+ if showHelp || flag.NArg() == 0 {
64+ flag.Usage()
65+ if showHelp {
66+ os.Exit(0)
67+ }
68+ os.Exit(1)
69+ }
70+
71+ xmlPath := flag.Arg(0)
72+
73+ // Derive output path if not specified
74+ if outputPath == "" {
75+ base := filepath.Base(xmlPath)
76+ base = strings.TrimSuffix(base, filepath.Ext(base))
77+ outputPath = base + "_generated.go"
78+ }
79+
80+ // Derive package name if not specified
81+ if packageName == "" {
82+ base := filepath.Base(xmlPath)
83+ base = strings.TrimSuffix(base, filepath.Ext(base))
84+
85+ // Clean up common prefixes/suffixes
86+ base = strings.TrimPrefix(base, "wlr-")
87+ base = strings.TrimPrefix(base, "zwlr-")
88+ base = strings.TrimSuffix(base, "-unstable-v1")
89+ base = strings.TrimSuffix(base, "-unstable-v2")
90+ base = strings.TrimSuffix(base, "-stable-v1")
91+
92+ // Convert to valid Go package name
93+ packageName = strings.ReplaceAll(base, "-", "_")
94+ }
95+
96+ // Create scanner
97+ s := scanner.NewScanner()
98+ s.CrossPackage = crossImports
99+
100+ // Parse XML
101+ if err := s.ParseXML(xmlPath); err != nil {
102+ log.Fatalf("Failed to parse XML: %v", err)
103+ }
104+
105+ // Generate code
106+ code, err := s.Generate(packageName)
107+ if err != nil {
108+ log.Fatalf("Failed to generate code: %v", err)
109+ }
110+
111+ // Write output with secure permissions
112+ if err := os.WriteFile(outputPath, code, 0600); err != nil {
113+ log.Fatalf("Failed to write output: %v", err)
114+ }
115+
116+ fmt.Printf("Generated %s (package %s)\n", outputPath, packageName)
117+}
118+
119+type crossFlags map[string]string
120+
121+func (f *crossFlags) String() string { return fmt.Sprint(map[string]string(*f)) }
122+func (f *crossFlags) Set(v string) error {
123+ parts := strings.SplitN(v, "=", 2)
124+ if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
125+ return fmt.Errorf("-import must be interface=GoImportPath")
126+ }
127+ if *f == nil {
128+ *f = make(map[string]string)
129+ }
130+ (*f)[parts[0]] = parts[1]
131+ return nil
132+}
A consumer_test.go +44 -0
@@ -0,0 +1,44 @@
1+//go:build linux
2+
3+package wlturbo
4+
5+import (
6+ "os"
7+ "os/exec"
8+ "path/filepath"
9+ "testing"
10+)
11+
12+// Run the public socketpair integration against a separate module, with no
13+// imports from internal/ and no worktree replacement outside its temp go.mod.
14+func TestExternalConsumer(t *testing.T) {
15+ goBin, err := exec.LookPath("go")
16+ if err != nil {
17+ t.Skip("go executable unavailable: external module integration requires go")
18+ }
19+ root, err := os.Getwd()
20+ if err != nil {
21+ t.Fatal(err)
22+ }
23+ temp := t.TempDir()
24+ mod := "module example.com/wlturbo-consumer\n\ngo 1.27\n\nrequire github.com/bnema/wlturbo v0.0.0\nreplace github.com/bnema/wlturbo => " + root + "\n"
25+ if err := os.WriteFile(filepath.Join(temp, "go.mod"), []byte(mod), 0600); err != nil {
26+ t.Fatal(err)
27+ }
28+ source, err := os.ReadFile(filepath.Join(root, "protocol/core/core_test.go"))
29+ if err != nil {
30+ t.Fatal(err)
31+ }
32+ // The external module has no access to the package's test helpers except
33+ // the ones copied into this temporary module. Compile and exercise only
34+ // the public integration scenarios, including typed key, frame and release.
35+ if err := os.WriteFile(filepath.Join(temp, "consumer_test.go"), source, 0600); err != nil {
36+ t.Fatal(err)
37+ }
38+ cmd := exec.Command(goBin, "test", "-run", "TestGeneratedCoreOverSocketpair|TestBufferReleaseAndOfferChild", "./...")
39+ cmd.Dir = temp
40+ cmd.Env = append(os.Environ(), "GOWORK=off", "GOFLAGS=-mod=mod")
41+ if output, err := cmd.CombinedOutput(); err != nil {
42+ t.Fatalf("external consumer: %v\n%s", err, output)
43+ }
44+}
M core.go +127 -379
@@ -3,10 +3,10 @@ package wlturbo
33 import (
44 "encoding/binary"
55 "errors"
6-
7- // "log"
86 "sync"
97 "sync/atomic"
8+
9+ "golang.org/x/sys/unix"
1010 )
1111
1212 // Context provides a compatibility layer for wl.Context
@@ -37,6 +37,8 @@ type Event struct {
3737 data []byte
3838 offset int
3939 display *Display
40+ fds []*OwnedFD
41+ fdIndex int
4042 }
4143
4244 // Data returns the raw event data
@@ -56,33 +58,75 @@ func NewContext(display *Display) *Context {
5658 }
5759 }
5860
59-// SendRequest sends a request through the context
61+// SendRequest sends a request through the context. The proxy is validated
62+// under the connection send lock, so no request reaches the wire after a
63+// destructor for the same proxy.
6064 func (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+ return c.SendRequestWithFDs(proxy, opcode, nil, args...)
6566 }
6667
67-// SendRequestWithFDs sends a request with file descriptors through the context
68+// SendRequestWithFDs sends a request with file descriptors through the context.
69+// On error the FDs remain owned by the caller.
6870 func (c *Context) SendRequestWithFDs(proxy Proxy, opcode uint32, fds []int, args ...interface{}) error {
71+ if err := c.CheckProxy(proxy); err != nil {
72+ return err
73+ }
74+ return c.display.sendRequest(proxy.ID(), uint16(opcode), fds, func() error { return c.CheckProxy(proxy) }, args)
75+}
76+
77+// CheckProxy rejects stale and foreign proxies before any bytes are written.
78+func (c *Context) CheckProxy(proxy Proxy) error {
6979 if c.closed.Load() {
7080 return errors.New("context is closed")
7181 }
72- return c.display.SendRequestWithFDs(proxy.ID(), uint16(opcode), fds, args...)
82+ if proxy == nil || proxy.ID() == 0 || proxy.Context() != c {
83+ return errors.New("invalid proxy")
84+ }
85+ registered, ok := c.proxies.Load(proxy.ID())
86+ if !ok || registered != proxy {
87+ return errors.New("proxy is not registered")
88+ }
89+ return nil
90+}
91+
92+// SendDestructor sends a destructor request exactly once. The proxy is
93+// claimed (unregistered) under the connection send lock, after marshaling
94+// and immediately before the write: concurrent destructors and later requests
95+// on the proxy fail without writing. A marshaling error leaves the proxy
96+// registered. A write error after the claim leaves the proxy unregistered;
97+// such an error means the connection is broken and must be closed.
98+func (c *Context) SendDestructor(proxy Proxy, opcode uint32, args ...interface{}) error {
99+ return c.SendDestructorWithFDs(proxy, opcode, nil, args...)
100+}
101+
102+// SendDestructorWithFDs is SendDestructor for requests carrying FDs. On error
103+// the FDs remain owned by the caller.
104+func (c *Context) SendDestructorWithFDs(proxy Proxy, opcode uint32, fds []int, args ...interface{}) error {
105+ if err := c.CheckProxy(proxy); err != nil {
106+ return err
107+ }
108+ return c.display.sendRequest(proxy.ID(), uint16(opcode), fds, func() error { return c.claimDestroy(proxy) }, args)
109+}
110+
111+func (c *Context) claimDestroy(proxy Proxy) error {
112+ if err := c.CheckProxy(proxy); err != nil {
113+ return err
114+ }
115+ if !c.proxies.CompareAndDelete(proxy.ID(), proxy) {
116+ return errors.New("proxy is not registered")
117+ }
118+ c.display.objects.CompareAndDelete(proxy.ID(), proxy)
119+ return nil
73120 }
74121
75122 // Register registers a proxy object
76123 func (c *Context) Register(proxy Proxy) {
77- if proxy != nil && proxy.ID() != 0 {
124+ if proxy != nil && proxy.ID() != 0 && !c.closed.Load() {
125+ if proxy.Context() != c {
126+ return
127+ }
78128 c.proxies.Store(proxy.ID(), proxy)
79129 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- // }
86130 }
87131 }
88132
@@ -221,19 +265,61 @@ func (e *Event) Array() []byte {
221265 return arr
222266 }
223267
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.
268+// OwnedFD owns one received descriptor. Close is idempotent; Take transfers
269+// ownership to the caller (who must close it). Descriptor zero is valid.
270+type OwnedFD struct {
271+ mu sync.Mutex
272+ fd int
273+}
274+
275+func (f *OwnedFD) Take() (int, error) {
276+ if f == nil {
277+ return -1, errors.New("missing descriptor")
278+ }
279+ f.mu.Lock()
280+ defer f.mu.Unlock()
281+ if f.fd < 0 {
282+ return -1, errors.New("descriptor already taken or closed")
283+ }
284+ n := f.fd
285+ f.fd = -1
286+ return n, nil
287+}
288+func (f *OwnedFD) Close() error {
289+ if f == nil {
290+ return nil
291+ }
292+ f.mu.Lock()
293+ defer f.mu.Unlock()
294+ if f.fd < 0 {
295+ return nil
296+ }
297+ n := f.fd
298+ f.fd = -1
299+ return unix.Close(n)
300+}
301+
302+// FD transfers ownership of the next descriptor assigned to this event.
303+// An unclaimed descriptor is closed when dispatch completes.
304+func (e *Event) FD() *OwnedFD {
305+ if e.fdIndex >= len(e.fds) {
306+ return nil
307+ }
308+ fd := e.fds[e.fdIndex]
309+ e.fds[e.fdIndex] = nil
310+ e.fdIndex++
311+ return fd
312+}
313+
314+// Fd is the legacy raw descriptor API; prefer FD for explicit ownership.
228315 func (e *Event) Fd() uintptr {
229- if e.display != nil {
230- if fd, ok := e.display.nextFD(); ok {
231- return uintptr(fd)
232- }
316+ fd := e.FD()
317+ if fd == nil {
318+ _ = e.Uint32()
319+ return 0
233320 }
234- // No descriptor was delivered: consume the placeholder argument.
235- _ = e.Uint32()
236- return 0
321+ n, _ := fd.Take()
322+ return uintptr(n)
237323 }
238324
239325 // NewId reads a new object ID from the event
@@ -278,32 +364,24 @@ type RegistryGlobalRemoveEvent struct {
278364
279365 // AddGlobalHandler adds a global handler to the registry
280366 func (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)
367+ if handler == nil {
368+ return
288369 }
289-
290370 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)
371+ if global, ok := r.FindGlobalByName(name); ok {
372+ handler.HandleRegistryGlobal(RegistryGlobalEvent{Registry: r, Name: name, Interface: global.Interface, Version: version})
300373 }
301374 })
302375 }
303376
304-// AddGlobalRemoveHandler adds a global remove handler (placeholder)
377+// AddGlobalRemoveHandler registers a handler for registry removals.
305378 func (r *Registry) AddGlobalRemoveHandler(handler RegistryGlobalRemoveHandler) {
306- // Would need to implement removal handling
379+ if handler == nil {
380+ return
381+ }
382+ r.mu.Lock()
383+ r.removeHandlers = append(r.removeHandlers, handler)
384+ r.mu.Unlock()
307385 }
308386
309387 // FindGlobalByName finds a global by its name ID
@@ -340,342 +418,12 @@ func (d *Display) Sync() (Object, error) {
340418 display: d,
341419 }
342420
343- // Send sync request (opcode 0)
421+ // Store before sending: the compositor may reply immediately.
422+ d.context.Register(callback)
344423 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
357-const (
358- SeatCapabilityPointer = 1
359- SeatCapabilityKeyboard = 2
360- SeatCapabilityTouch = 4
361-)
362-
363-// Seat represents a wl_seat
364-type Seat struct {
365- BaseProxy
366- capabilities uint32
367- name string
368-}
369-
370-// SeatCapabilitiesHandler handles seat capabilities events
371-type SeatCapabilitiesHandler interface {
372- HandleSeatCapabilities(seat *Seat, capabilities uint32)
373-}
374-
375-// SeatNameHandler handles seat name events
376-type SeatNameHandler interface {
377- HandleSeatName(seat *Seat, name string)
378-}
379-
380-// NewSeat creates a new seat proxy
381-func NewSeat(ctx *Context) *Seat {
382- return &Seat{
383- BaseProxy: BaseProxy{
384- context: ctx,
385- },
386- }
387-}
388-
389-// GetPointer gets the pointer device
390-func (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
412-func (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
434-func (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
456-func (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
465-func (s *Seat) Capabilities() uint32 {
466- return s.capabilities
467-}
468-
469-// Name returns the seat name
470-func (s *Seat) Name() string {
471- return s.name
472-}
473-
474-// Dispatch handles events for the seat
475-func (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
488-type Surface struct {
489- BaseProxy
490-}
491-
492-// NewSurface creates a new surface proxy
493-func NewSurface(ctx *Context) *Surface {
494- return &Surface{
495- BaseProxy: BaseProxy{
496- context: ctx,
497- },
498- }
499-}
500-
501-// Destroy destroys the surface
502-func (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
511-func (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
516-func (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
521-func (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)
424+ d.context.Unregister(callback)
534425 return nil, err
535426 }
536427
537428 return callback, nil
538429 }
539-
540-// SetOpaqueRegion sets the opaque region
541-func (s *Surface) SetOpaqueRegion(region *Region) error {
542- return s.context.SendRequest(s, 4, region) // opcode 4
543-}
544-
545-// SetInputRegion sets the input region
546-func (s *Surface) SetInputRegion(region *Region) error {
547- return s.context.SendRequest(s, 5, region) // opcode 5
548-}
549-
550-// Commit commits pending surface state
551-func (s *Surface) Commit() error {
552- return s.context.SendRequest(s, 6) // opcode 6
553-}
554-
555-// SetBufferTransform sets the buffer transform
556-func (s *Surface) SetBufferTransform(transform int32) error {
557- return s.context.SendRequest(s, 7, transform) // opcode 7
558-}
559-
560-// SetBufferScale sets the buffer scale
561-func (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
566-func (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
571-func (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
576-func (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
582-type Pointer struct {
583- BaseProxy
584-}
585-
586-// Keyboard represents a wl_keyboard
587-type Keyboard struct {
588- BaseProxy
589-}
590-
591-// Touch represents a wl_touch
592-type Touch struct {
593- BaseProxy
594-}
595-
596-// Output represents a wl_output
597-type Output struct {
598- BaseProxy
599-}
600-
601-// Region represents a wl_region
602-type Region struct {
603- BaseProxy
604-}
605-
606-// Add adds a rectangle to the region
607-func (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
612-func (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
617-func (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
626-type Compositor struct {
627- BaseProxy
628-}
629-
630-// NewCompositor creates a new compositor proxy
631-func NewCompositor(ctx *Context) *Compositor {
632- return &Compositor{
633- BaseProxy: BaseProxy{
634- context: ctx,
635- },
636- }
637-}
638-
639-// CreateSurface creates a new surface
640-func (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
662-func (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-}
A docs/protocol-migration.md +29 -0
@@ -0,0 +1,29 @@
1+# Canonical core / migration (v0.x)
2+
3+`protocol/core` replaces root-package and `wl` shim core object types. Bootstrap
4+`Display`, `Registry`, `Context`, and `BaseProxy` remain in the root/`wl`
5+transport. Bind globals using `core.NewCompositor(display.Context())`,
6+`core.NewSeat(display.Context())`, etc. and `display.Registry().Bind(name,
7+core.CompositorInterface, version, compositor)`. Requests create typed registered
8+children (`seat.GetKeyboard()`, `surface.Frame()`, `shm.CreatePool()`) and
9+server-side `new_id` events register concrete objects before invoking handlers.
10+Do not overwrite registered objects with a second wrapper.
11+
12+Breaking changes: `wlturbo`/`wl` `Seat`, `Surface`, `Pointer`, `Keyboard`,
13+`Touch`, `Output`, `Region`, `Compositor`, their constructors, and seat
14+capability aliases have moved to `protocol/core`. Generated methods accept
15+concrete core objects and return concrete children, not `wl.Object`. Legacy
16+raw `Event.Fd()` is not for generated events; generated FD handlers receive
17+`*wlturbo.OwnedFD` with `Take() (int,error)` or `Close()`; second and later
18+handlers receive nil FD. On a successful full send a generated FD request
19+closes the local descriptor; on error the caller keeps it. `Registry.BindID`
20+remains for legacy callers but cannot register typed event dispatch: prefer
21+`Bind` and generated constructors. Unrecognized custom-proxy event opcodes
22+now fail unless declared via `Display.RegisterEventSignature`.
23+
24+The scanner accepts `-import wl_surface=github.com/bnema/wlturbo/protocol/core`
25+(repeat for each external type); local XML definitions take precedence, then
26+explicit external mapping, then only bootstrap display/registry. Generate with
27+`GOWORK=off go generate ./...`; metadata and XML license are in
28+`protocol/SOURCE.md`. P3 extensions will use the same scanner and public core
29+mappings; no generator-owned compositor policy is introduced here.
M event_pool.go +0 -34
@@ -27,10 +27,6 @@ type EventDispatcher struct {
2727
2828 // Extended handlers for object IDs >= 1024 (rare case)
2929 extHandlers sync.Map
30-
31- // Pre-allocated event objects to avoid allocations
32- eventCache [64]Event
33- eventIndex atomic.Uint32
3430 }
3531
3632 // handlerEntry stores handlers for a specific object
@@ -166,36 +162,6 @@ type RawEvent struct {
166162 Data []byte
167163 }
168164
169-// DirectDispatcher provides the absolute fastest dispatch path for hot events
170-type DirectDispatcher struct {
171- // Direct function pointers for the hottest paths (e.g., pointer motion)
172- pointerMotion func(surfaceX, surfaceY Fixed)
173- pointerButton func(button, state uint32)
174- keyboardKey func(key, state uint32)
175- frameCallback func(callbackData uint32)
176-
177- // Fallback to regular dispatcher
178- fallback *EventDispatcher
179-}
180-
181-// DispatchPointerMotion dispatches pointer motion events with zero overhead
182-//
183-//go:inline
184-func (d *DirectDispatcher) DispatchPointerMotion(surfaceX, surfaceY Fixed) {
185- if d.pointerMotion != nil {
186- d.pointerMotion(surfaceX, surfaceY)
187- }
188-}
189-
190-// DispatchPointerButton dispatches pointer button events with zero overhead
191-//
192-//go:inline
193-func (d *DirectDispatcher) DispatchPointerButton(button, state uint32) {
194- if d.pointerButton != nil {
195- d.pointerButton(button, state)
196- }
197-}
198-
199165 // Ensure cache line alignment for hot data structures
200166 type cacheLinePad [64]byte
201167
M fd_test.go +92 -48
@@ -90,6 +90,8 @@ func newFDDisplay(t *testing.T, client *net.UnixConn) (*Display, *[]int) {
9090
9191 d := newDisplay(client)
9292 fds := []int{}
93+ d.RegisterEventSignature(7, 3, "uint,fd,")
94+ d.RegisterEventSignature(7, 4, "uint,")
9395 d.objects.Store(uint32(7), &fdRecordingProxy{
9496 BaseProxy: BaseProxy{id: 7, context: d.context},
9597 fds: &fds,
@@ -146,49 +148,19 @@ func TestDisplayFD_Single(t *testing.T) {
146148 readSentinel(t, r, "single-fd-sentinel")
147149 }
148150
149-func TestDisplayFD_MultipleInOneControlMessage(t *testing.T) {
151+func TestDisplayFD_ExtraDescriptorRejected(t *testing.T) {
150152 client, peer := unixSocketPair(t)
151- d, received := newFDDisplay(t, client)
152-
153- firstR, firstW, err := os.Pipe()
154- if err != nil {
155- t.Fatalf("pipe: %v", err)
156- }
157- defer firstR.Close()
158- secondR, secondW, err := os.Pipe()
153+ d, _ := newFDDisplay(t, client)
154+ r, w, err := os.Pipe()
159155 if err != nil {
160- t.Fatalf("pipe: %v", err)
156+ t.Fatal(err)
161157 }
162- defer secondR.Close()
163-
164- sendMessageWithFDs(t, peer, message(7, 3, []byte{0, 0, 0, 0}),
165- []int{int(firstW.Fd()), int(secondW.Fd())})
166- firstW.Close()
167- secondW.Close()
168-
169- if err := d.Dispatch(); err != nil {
170- t.Fatalf("Dispatch: %v", err)
171- }
172-
173- // A handler consumes one descriptor per fd argument, so the test drains
174- // whatever the connection still holds, in arrival order.
175- for {
176- fd, ok := d.nextFD()
177- if !ok {
178- break
179- }
180- *received = append(*received, fd)
181- }
182-
183- if len(*received) != 2 {
184- t.Fatalf("received %d descriptors, want 2", len(*received))
158+ defer r.Close()
159+ defer w.Close()
160+ sendMessageWithFDs(t, peer, message(7, 3, nil), []int{int(w.Fd()), int(w.Fd())})
161+ if err := d.Dispatch(); !errors.Is(err, ErrMalformedFrame) {
162+ t.Fatalf("extra FD: %v", err)
185163 }
186-
187- writeSentinel(t, uintptr((*received)[0]), "first-pipe")
188- writeSentinel(t, uintptr((*received)[1]), "second-pipe")
189-
190- readSentinel(t, firstR, "first-pipe")
191- readSentinel(t, secondR, "second-pipe")
192164 }
193165
194166 func TestDisplayFD_EventWithoutFDsAfterOneWithFDs(t *testing.T) {
@@ -304,9 +276,9 @@ func TestDisplayFD_TruncatedControlData(t *testing.T) {
304276 }
305277
306278 // A truncated control message must not leave descriptors queued.
307- d.fdMu.Lock()
279+ d.recvMu.Lock()
308280 pending := len(d.pendingFDs)
309- d.fdMu.Unlock()
281+ d.recvMu.Unlock()
310282 if pending != 0 {
311283 t.Fatalf("%d descriptors still queued after a truncated control message", pending)
312284 }
@@ -315,6 +287,7 @@ func TestDisplayFD_TruncatedControlData(t *testing.T) {
315287 func TestDisplayFD_UnconsumedDescriptorClosedOnShutdown(t *testing.T) {
316288 client, peer := unixSocketPair(t)
317289 d := newDisplay(client)
290+ d.RegisterEventSignature(7, 3, "uint,fd,")
318291 d.objects.Store(uint32(7), &fdIgnoringProxy{
319292 BaseProxy: BaseProxy{id: 7, context: d.context},
320293 })
@@ -333,12 +306,12 @@ func TestDisplayFD_UnconsumedDescriptorClosedOnShutdown(t *testing.T) {
333306 t.Fatalf("Dispatch: %v", err)
334307 }
335308
336- // While the display still holds the descriptor the pipe stays open.
337- if err := r.SetReadDeadline(time.Now().Add(200 * time.Millisecond)); err != nil {
338- t.Fatalf("SetReadDeadline: %v", err)
309+ // An ignored FD is closed at the end of the event, not at shutdown.
310+ if err := r.SetReadDeadline(time.Now().Add(2 * time.Second)); err != nil {
311+ t.Fatal(err)
339312 }
340- if _, err := r.Read(make([]byte, 1)); !errors.Is(err, os.ErrDeadlineExceeded) {
341- t.Fatalf("read with the display holding the descriptor = %v, want %v", err, os.ErrDeadlineExceeded)
313+ if _, err := r.Read(make([]byte, 1)); !errors.Is(err, io.EOF) {
314+ t.Fatalf("ignored FD: %v", err)
342315 }
343316
344317 // Closing the display must release it, so the read end sees EOF.
@@ -421,6 +394,7 @@ func runFDLeakIteration(t *testing.T, devNull *os.File) {
421394
422395 client, peer := rawSocketPair(t)
423396 d := newDisplay(client)
397+ d.RegisterEventSignature(7, 3, "uint,fd,")
424398 d.objects.Store(uint32(7), &fdIgnoringProxy{
425399 BaseProxy: BaseProxy{id: 7, context: d.context},
426400 })
@@ -448,6 +422,7 @@ func runFDLeakIteration(t *testing.T, devNull *os.File) {
448422 func TestDisplayFD_ParseFailureClosesQueuedDescriptor(t *testing.T) {
449423 client, peer := unixSocketPair(t)
450424 d := newDisplay(client)
425+ d.RegisterEventSignature(7, 3, "uint,fd,")
451426 d.objects.Store(uint32(7), &fdIgnoringProxy{
452427 BaseProxy: BaseProxy{id: 7, context: d.context},
453428 })
@@ -467,9 +442,9 @@ func TestDisplayFD_ParseFailureClosesQueuedDescriptor(t *testing.T) {
467442 t.Fatalf("Dispatch error = %v, want %v", err, ErrMalformedFrame)
468443 }
469444
470- d.fdMu.Lock()
445+ d.recvMu.Lock()
471446 pending := len(d.pendingFDs)
472- d.fdMu.Unlock()
447+ d.recvMu.Unlock()
473448 if pending != 0 {
474449 t.Fatalf("%d descriptors still queued after a malformed frame", pending)
475450 }
@@ -481,3 +456,72 @@ func TestDisplayFD_ParseFailureClosesQueuedDescriptor(t *testing.T) {
481456 t.Fatalf("read after malformed frame = %v, want %v", err, io.EOF)
482457 }
483458 }
459+
460+// Closing in a callback must not wait for the dispatch reader's state lock.
461+type closingProxy struct {
462+ BaseProxy
463+ d *Display
464+ returned bool
465+}
466+
467+func (p *closingProxy) EventSignature(op uint16) (string, bool) { return "fd,", op == 0 }
468+func (p *closingProxy) Dispatch(*Event) { p.returned = p.d.Close() == nil }
469+
470+func TestCloseInsideHandlerClosesQueuedFDs(t *testing.T) {
471+ client, peer := unixSocketPair(t)
472+ d := newDisplay(client)
473+ proxy := &closingProxy{BaseProxy: BaseProxy{id: 7, context: d.context}, d: d}
474+ d.context.Register(proxy)
475+ r, w, err := os.Pipe()
476+ if err != nil {
477+ t.Fatal(err)
478+ }
479+ defer r.Close()
480+ extra, xw, err := os.Pipe()
481+ if err != nil {
482+ t.Fatal(err)
483+ }
484+ defer extra.Close()
485+ // Two frames in one read; second FD remains queued while first handler runs.
486+ payload := append(message(7, 0, nil), message(7, 0, nil)...)
487+ sendMessageWithFDs(t, peer, payload, []int{int(w.Fd()), int(xw.Fd())})
488+ w.Close()
489+ xw.Close()
490+ if err := d.Dispatch(); err != nil {
491+ t.Fatal(err)
492+ }
493+ if !proxy.returned {
494+ t.Fatal("Close did not return from handler")
495+ }
496+ for _, reader := range []*os.File{r, extra} {
497+ if err := reader.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
498+ t.Fatal(err)
499+ }
500+ if _, err := reader.Read(make([]byte, 1)); !errors.Is(err, io.EOF) {
501+ t.Fatalf("fd not closed: %v", err)
502+ }
503+ }
504+}
505+
506+func TestParseFDsKeepsRightsBeforeMalformedHeader(t *testing.T) {
507+ r, w, err := os.Pipe()
508+ if err != nil {
509+ t.Fatal(err)
510+ }
511+ defer r.Close()
512+ defer w.Close()
513+ rights := unix.UnixRights(int(w.Fd()))
514+ oob := append(rights, make([]byte, unix.SizeofCmsghdr)...)
515+ fds, err := parseFDs(oob)
516+ if err == nil || len(fds) != 1 {
517+ t.Fatalf("fds=%v err=%v", fds, err)
518+ }
519+ if _, err := unix.FcntlInt(uintptr(fds[0]), unix.F_GETFD, 0); err != nil {
520+ t.Fatal(err)
521+ }
522+ for _, fd := range fds {
523+ if err := unix.Close(fd); err != nil {
524+ t.Fatal(err)
525+ }
526+ }
527+}
A fd_zero_test.go +14 -0
@@ -0,0 +1,14 @@
1+package wlturbo
2+
3+import "testing"
4+
5+func TestOwnedFDZero(t *testing.T) {
6+ fd := &OwnedFD{fd: 0}
7+ n, e := fd.Take()
8+ if e != nil || n != 0 {
9+ t.Fatalf("Take fd zero = %d %v", n, e)
10+ }
11+ if e := fd.Close(); e != nil {
12+ t.Fatal(e)
13+ }
14+}
A internal/scanner/scanner.go +731 -0
@@ -0,0 +1,731 @@
1+// Package scanner generates WLTurbo-backed Go bindings from Wayland protocol
2+// XML files.
3+//
4+// The generated code owns protocol semantics only: it embeds wl.BaseProxy,
5+// allocates and registers child objects through the wl.Context, sends requests
6+// through that context, and dispatches typed events to registered handlers.
7+// Message framing, descriptor passing and connection lifetime stay in the
8+// transport, so a generated binding never opens a socket or writes a header.
9+package scanner
10+
11+import (
12+ "bytes"
13+ "encoding/xml"
14+ "fmt"
15+ "go/format"
16+ "io"
17+ "os"
18+ "path/filepath"
19+ "sort"
20+ "strings"
21+ "text/template"
22+)
23+
24+// TransportImport is the import path generated bindings use for the Wayland
25+// transport.
26+const TransportImport = "github.com/bnema/wlturbo/wl"
27+
28+// Protocol represents a Wayland protocol specification
29+type Protocol struct {
30+ XMLName xml.Name `xml:"protocol"`
31+ Name string `xml:"name,attr"`
32+ Copyright string `xml:"copyright"`
33+ Description *Description `xml:"description"`
34+ Interfaces []Interface `xml:"interface"`
35+}
36+
37+// Interface represents a Wayland interface in the protocol
38+type Interface struct {
39+ Name string `xml:"name,attr"`
40+ Version int `xml:"version,attr"`
41+ Description *Description `xml:"description"`
42+ Requests []Request `xml:"request"`
43+ Events []Event `xml:"event"`
44+ Enums []Enum `xml:"enum"`
45+}
46+
47+// Request represents a client-to-server message
48+type Request struct {
49+ Name string `xml:"name,attr"`
50+ Type string `xml:"type,attr"` // "destructor" or empty
51+ Since int `xml:"since,attr"`
52+ Description *Description `xml:"description"`
53+ Args []Arg `xml:"arg"`
54+}
55+
56+// Event represents a server-to-client message
57+type Event struct {
58+ Type string `xml:"type,attr"`
59+ Name string `xml:"name,attr"`
60+ Since int `xml:"since,attr"`
61+ Description *Description `xml:"description"`
62+ Args []Arg `xml:"arg"`
63+}
64+
65+// Arg represents a message argument
66+type Arg struct {
67+ Name string `xml:"name,attr"`
68+ Type string `xml:"type,attr"`
69+ Summary string `xml:"summary,attr"`
70+ Interface string `xml:"interface,attr"`
71+ AllowNull bool `xml:"allow-null,attr"`
72+ Enum string `xml:"enum,attr"`
73+}
74+
75+// Enum represents an enumeration type
76+type Enum struct {
77+ Name string `xml:"name,attr"`
78+ Since int `xml:"since,attr"`
79+ Bitfield bool `xml:"bitfield,attr"`
80+ Description *Description `xml:"description"`
81+ Entries []Entry `xml:"entry"`
82+}
83+
84+// Entry represents an enum value
85+type Entry struct {
86+ Name string `xml:"name,attr"`
87+ Value string `xml:"value,attr"`
88+ Summary string `xml:"summary,attr"`
89+ Since int `xml:"since,attr"`
90+}
91+
92+// Description represents documentation
93+type Description struct {
94+ Summary string `xml:"summary,attr"`
95+ Text string `xml:",chardata"`
96+}
97+
98+// Scanner generates Go bindings from Wayland protocol XML
99+type Scanner struct {
100+ protocol *Protocol
101+ source string
102+ // CrossPackage maps interface names to Go package import paths. Local wins.
103+ CrossPackage map[string]string
104+}
105+
106+// NewScanner creates a new protocol scanner
107+func NewScanner() *Scanner {
108+ return &Scanner{}
109+}
110+
111+// ParseXML parses a Wayland protocol XML file
112+func (s *Scanner) ParseXML(path string) error {
113+ file, err := os.Open(path)
114+ if err != nil {
115+ return fmt.Errorf("failed to open XML file: %w", err)
116+ }
117+ defer func() { _ = file.Close() }()
118+
119+ data, err := io.ReadAll(file)
120+ if err != nil {
121+ return fmt.Errorf("failed to read XML file: %w", err)
122+ }
123+
124+ var protocol Protocol
125+ if err := xml.Unmarshal(data, &protocol); err != nil {
126+ return fmt.Errorf("failed to parse XML: %w", err)
127+ }
128+
129+ s.protocol = &protocol
130+ // Only the base name is recorded: generated output must not depend on the
131+ // path the XML happened to be read from.
132+ s.source = filepath.Base(path)
133+ return nil
134+}
135+
136+// Generate creates Go source code for the parsed protocol
137+func (s *Scanner) Generate(packageName string) ([]byte, error) {
138+ if s.protocol == nil {
139+ return nil, fmt.Errorf("no protocol parsed")
140+ }
141+
142+ data, err := s.prepareTemplateData(packageName)
143+ if err != nil {
144+ return nil, err
145+ }
146+
147+ var buf bytes.Buffer
148+ tmpl := template.Must(template.New("protocol").Parse(protocolTemplate))
149+ if err := tmpl.Execute(&buf, data); err != nil {
150+ return nil, fmt.Errorf("failed to execute template: %w", err)
151+ }
152+
153+ formatted, err := format.Source(buf.Bytes())
154+ if err != nil {
155+ return buf.Bytes(), fmt.Errorf("failed to format generated code: %w", err)
156+ }
157+ return formatted, nil
158+}
159+
160+// templateData holds data for template generation
161+type templateData struct {
162+ Package string
163+ Protocol string
164+ Source string
165+ ImportBlock string
166+ Constants []constantData
167+ Interfaces []interfaceData
168+}
169+
170+type interfaceData struct {
171+ Name string
172+ GoName string
173+ Version int
174+ HasEvents bool
175+ SignatureCases []signatureCase
176+ Requests []requestData
177+ Events []eventData
178+}
179+
180+type signatureCase struct {
181+ Opcode int
182+ Signature string
183+}
184+type requestData struct {
185+ Name string
186+ GoName string
187+ Doc string
188+ Opcode int
189+ Destructor bool
190+ Params string
191+ Results string
192+ ArgPreparations []string
193+ ArgExprs []string
194+ CreatesChild bool
195+ ChildVar string
196+ ChildType string
197+ ChildFactory string
198+ ErrorReturn string
199+ FDArgs []string
200+ HasFDs bool
201+ SendCall string
202+}
203+
204+// argSlot is one positional argument of a request, in declaration order. A
205+// new_id slot is filled in once the child object has been created.
206+type argSlot struct {
207+ newID bool
208+ expr string
209+}
210+
211+type eventData struct {
212+ HasFD bool
213+ ServerDestroy bool
214+ FDNames []string
215+ Name string
216+ GoName string
217+ Opcode int
218+ HandlerField string
219+ HandlerAccessor string
220+ HandlerParams string
221+ HandlerArgs string
222+ DecodeLines []string
223+}
224+
225+type constantData struct {
226+ Name string
227+ Value string
228+ Type string
229+ Enum string
230+ Entry string
231+}
232+
233+// Only display and registry have handwritten bootstrap bindings.
234+var bootstrapTypes = map[string]bool{
235+ "Display": true,
236+ "Registry": true,
237+}
238+
239+// goKeywords are avoided as parameter names because generated identifiers
240+// must not collide with the language.
241+var goKeywords = map[string]bool{
242+ "break": true, "case": true, "chan": true, "const": true, "continue": true,
243+ "default": true, "defer": true, "else": true, "fallthrough": true, "for": true,
244+ "func": true, "go": true, "goto": true, "if": true, "import": true,
245+ "interface": true, "map": true, "package": true, "range": true, "return": true,
246+ "select": true, "struct": true, "switch": true, "type": true, "var": true,
247+}
248+
249+func (s *Scanner) prepareTemplateData(packageName string) (templateData, error) {
250+ data := templateData{
251+ Package: packageName,
252+ Protocol: s.protocol.Name,
253+ Source: s.source,
254+ }
255+
256+ imports := []string{TransportImport}
257+ usedCross := make(map[string]bool)
258+ for _, candidate := range s.protocol.Interfaces {
259+ for _, req := range candidate.Requests {
260+ for _, arg := range req.Args {
261+ if arg.Type == "object" || arg.Type == "new_id" {
262+ usedCross[arg.Interface] = true
263+ }
264+ }
265+ }
266+ for _, ev := range candidate.Events {
267+ for _, arg := range ev.Args {
268+ if arg.Type == "object" || arg.Type == "new_id" {
269+ usedCross[arg.Interface] = true
270+ }
271+ }
272+ }
273+ }
274+ for iface, path := range s.CrossPackage {
275+ local := false
276+ for _, candidate := range s.protocol.Interfaces {
277+ if candidate.Name == iface {
278+ local = true
279+ break
280+ }
281+ }
282+ if local || !usedCross[iface] || bootstrapTypes[s.toGoName(iface)] {
283+ continue
284+ }
285+ alias := "cross_" + strings.ReplaceAll(iface, "-", "_")
286+ imports = append(imports, alias+" "+path)
287+ }
288+ sort.Strings(imports)
289+ stdImports := []string{}
290+ needsSync := false
291+
292+ // Enum constants are package-level declarations, so two interfaces that
293+ // declare the same enum entry (for example xdg_surface.error.invalid_size
294+ // and xdg_toplevel.error.invalid_size) would collide. Entries whose name
295+ // occurs in more than one interface are qualified with their interface
296+ // name below; unique names keep their historical, unprefixed form.
297+ enumEntryCounts := map[string]int{}
298+ for _, iface := range s.protocol.Interfaces {
299+ for _, enum := range iface.Enums {
300+ for _, entry := range enum.Entries {
301+ enumEntryCounts[constantName(enum.Name, entry.Name)]++
302+ }
303+ }
304+ }
305+
306+ for _, iface := range s.protocol.Interfaces {
307+ if packageName == "core" && (iface.Name == "wl_display" || iface.Name == "wl_registry") {
308+ continue
309+ }
310+ ifaceData, err := s.processInterface(iface)
311+ if err != nil {
312+ return data, err
313+ }
314+ data.Interfaces = append(data.Interfaces, ifaceData)
315+ if len(iface.Events) > 0 {
316+ needsSync = true
317+ }
318+
319+ for _, enum := range iface.Enums {
320+ for _, entry := range enum.Entries {
321+ name := constantName(enum.Name, entry.Name)
322+ if enumEntryCounts[name] > 1 {
323+ name = qualifiedConstantName(iface.Name, enum.Name, entry.Name)
324+ }
325+ // Enum values are emitted untyped: the same enum travels as a uint
326+ // in one protocol and as an int in another, and an untyped constant
327+ // assigns to either without casts at the call site.
328+ data.Constants = append(data.Constants, constantData{
329+ Name: name,
330+ Value: entry.Value,
331+ Enum: enum.Name,
332+ Entry: entry.Name,
333+ })
334+ }
335+ }
336+ }
337+
338+ // Handlers are registered and dispatched from different goroutines, so an
339+ // interface with events needs a mutex.
340+ if needsSync {
341+ stdImports = append(stdImports, "sync")
342+ }
343+
344+ var block strings.Builder
345+ block.WriteString("import (\n")
346+ for _, imp := range stdImports {
347+ fmt.Fprintf(&block, "\t%q\n", imp)
348+ }
349+ if len(stdImports) > 0 && len(imports) > 0 {
350+ block.WriteString("\n")
351+ }
352+ for _, imp := range imports {
353+ if strings.Contains(imp, " ") {
354+ parts := strings.SplitN(imp, " ", 2)
355+ fmt.Fprintf(&block, "\t%s %q\n", parts[0], parts[1])
356+ } else {
357+ fmt.Fprintf(&block, "\t%q\n", imp)
358+ }
359+ }
360+ block.WriteString(")")
361+ data.ImportBlock = block.String()
362+
363+ return data, nil
364+}
365+
366+func (s *Scanner) processInterface(iface Interface) (interfaceData, error) {
367+ data := interfaceData{
368+ Name: iface.Name,
369+ GoName: s.toGoName(iface.Name),
370+ Version: iface.Version,
371+ HasEvents: len(iface.Events) > 0,
372+ }
373+
374+ for i, req := range iface.Requests {
375+ request, err := s.processRequest(iface, req, i)
376+ if err != nil {
377+ return data, err
378+ }
379+ data.Requests = append(data.Requests, request)
380+ }
381+
382+ for i, event := range iface.Events {
383+ var sig strings.Builder
384+ for _, arg := range event.Args {
385+ sig.WriteString(arg.Type)
386+ sig.WriteByte(',')
387+ }
388+ data.SignatureCases = append(data.SignatureCases, signatureCase{i, sig.String()})
389+ eventData, err := s.processEvent(event, i)
390+ if iface.Name == "wl_callback" && event.Name == "done" {
391+ eventData.ServerDestroy = true
392+ }
393+ if err != nil {
394+ return data, err
395+ }
396+ data.Events = append(data.Events, eventData)
397+ }
398+
399+ return data, nil
400+}
401+
402+func (s *Scanner) processRequest(iface Interface, req Request, opcode int) (requestData, error) {
403+ data := requestData{
404+ Name: req.Name,
405+ GoName: s.toGoName(req.Name),
406+ Doc: s.formatDescription(req.Description),
407+ Opcode: opcode,
408+ Destructor: req.Type == "destructor",
409+ ChildVar: "child",
410+ }
411+
412+ var (
413+ params []string
414+ argSlots []argSlot
415+ argExprs []string
416+ fdExprs []string
417+ newIDArg *Arg
418+ )
419+
420+ for i, arg := range req.Args {
421+ if arg.Type == "new_id" {
422+ if newIDArg != nil {
423+ return data, fmt.Errorf("%s.%s: more than one new_id argument is not supported", iface.Name, req.Name)
424+ }
425+ newIDArg = &arg
426+ // The child object is created later, but it keeps its declared
427+ // position: the wire order is the argument order.
428+ argSlots = append(argSlots, argSlot{newID: true})
429+ continue
430+ }
431+
432+ name := s.paramName(arg.Name, i)
433+ goType, err := s.goTypeForArg(arg)
434+ if err != nil {
435+ return data, fmt.Errorf("%s.%s: %w", iface.Name, req.Name, err)
436+ }
437+
438+ expr := name
439+ switch arg.Type {
440+ case "object":
441+ // Objects are pointer types, and travel as wl.Object so a nil
442+ // pointer is never sent as a non-nil interface holding a typed nil.
443+ params = append(params, name+" *"+goType)
444+ holder := fmt.Sprintf("arg%d", i)
445+ data.ArgPreparations = append(data.ArgPreparations,
446+ fmt.Sprintf("var %s wl.Object", holder),
447+ fmt.Sprintf("if %s != nil {", name),
448+ fmt.Sprintf("\t%s = %s", holder, name),
449+ "}")
450+ expr = holder
451+
452+ case "fd":
453+ // Descriptors travel out of band: the body carries no value for
454+ // them, and the descriptor list is attached to the request.
455+ params = append(params, name+" int")
456+ fdExprs = append(fdExprs, name)
457+ data.FDArgs = append(data.FDArgs, name)
458+ expr = "uintptr(" + name + ")"
459+
460+ default:
461+ params = append(params, name+" "+goType)
462+ }
463+
464+ argSlots = append(argSlots, argSlot{expr: expr})
465+ }
466+
467+ if newIDArg != nil {
468+ childType, err := s.goTypeForArg(*newIDArg)
469+ if err != nil {
470+ return data, fmt.Errorf("%s.%s: %w", iface.Name, req.Name, err)
471+ }
472+ data.CreatesChild = true
473+ data.ChildType = childType
474+ if strings.Contains(childType, ".") {
475+ data.ChildFactory = childType[:strings.LastIndex(childType, ".")+1] + "New" + s.toGoName(newIDArg.Interface)
476+ }
477+ }
478+
479+ for _, slot := range argSlots {
480+ if slot.newID {
481+ argExprs = append(argExprs, data.ChildVar)
482+ continue
483+ }
484+ argExprs = append(argExprs, slot.expr)
485+ }
486+
487+ data.Params = strings.Join(params, ", ")
488+ data.ArgExprs = argExprs
489+ args := ""
490+ if len(argExprs) > 0 {
491+ args = ", " + strings.Join(argExprs, ", ")
492+ }
493+ // Destructors claim the proxy atomically before sending so a concurrent
494+ // second destroy cannot reach the wire.
495+ send := "SendRequest"
496+ if data.Destructor {
497+ send = "SendDestructor"
498+ }
499+ if len(fdExprs) > 0 {
500+ data.HasFDs = true
501+ data.SendCall = fmt.Sprintf("%sWithFDs(o, %d, []int{%s}%s)", send, opcode, strings.Join(fdExprs, ", "), args)
502+ } else {
503+ data.SendCall = fmt.Sprintf("%s(o, %d%s)", send, opcode, args)
504+ }
505+ if data.CreatesChild {
506+ data.Results = "(*" + data.ChildType + ", error)"
507+ data.ErrorReturn = "nil, err"
508+ } else {
509+ data.Results = "error"
510+ data.ErrorReturn = "err"
511+ }
512+
513+ return data, nil
514+}
515+
516+func (s *Scanner) processEvent(event Event, opcode int) (eventData, error) {
517+ goName := s.toGoName(event.Name)
518+ data := eventData{
519+ ServerDestroy: event.Type == "destructor",
520+ Name: event.Name,
521+ GoName: goName,
522+ Opcode: opcode,
523+ HandlerField: "on" + goName,
524+ HandlerAccessor: "handlersFor" + goName,
525+ }
526+
527+ var params, args []string
528+
529+ for i, arg := range event.Args {
530+ name := s.paramName(arg.Name, i)
531+
532+ switch arg.Type {
533+ case "new_id":
534+ childType, err := s.goTypeForArg(arg)
535+ if err != nil {
536+ return data, fmt.Errorf("%s.%s: %w", event.Name, arg.Name, err)
537+ }
538+ child := name + "Object"
539+ data.DecodeLines = append(data.DecodeLines, fmt.Sprintf("%sID := event.Uint32()", name))
540+ if strings.Contains(childType, ".") {
541+ factory := childType[:strings.LastIndex(childType, ".")+1] + "New" + s.toGoName(arg.Interface)
542+ data.DecodeLines = append(data.DecodeLines, fmt.Sprintf("%s := %s(o.Context())", child, factory))
543+ } else {
544+ data.DecodeLines = append(data.DecodeLines,
545+ fmt.Sprintf("%s := &%s{}", child, childType),
546+ fmt.Sprintf("%s.SetContext(o.Context())", child),
547+ )
548+ }
549+ data.DecodeLines = append(data.DecodeLines,
550+ fmt.Sprintf("%s.SetID(%sID)", child, name),
551+ fmt.Sprintf("o.Context().Register(%s)", child),
552+ )
553+ params = append(params, child+" *"+childType)
554+ args = append(args, child)
555+
556+ case "object":
557+ // Object references are reported as raw object IDs: the generated
558+ // layer does not own a registry of foreign proxies.
559+ data.DecodeLines = append(data.DecodeLines, fmt.Sprintf("%sID := event.Uint32()", name))
560+ params = append(params, name+"ID uint32")
561+ args = append(args, name+"ID")
562+
563+ default:
564+ if arg.Type == "fd" {
565+ data.HasFD = true
566+ data.FDNames = append(data.FDNames, name)
567+ }
568+ goType, decoder, err := s.scalarDecoder(arg)
569+ if err != nil {
570+ return data, fmt.Errorf("%s.%s: %w", event.Name, arg.Name, err)
571+ }
572+ data.DecodeLines = append(data.DecodeLines, fmt.Sprintf("%s := event.%s()", name, decoder))
573+ params = append(params, name+" "+goType)
574+ args = append(args, name)
575+ }
576+ }
577+
578+ data.HandlerParams = strings.Join(params, ", ")
579+ data.HandlerArgs = strings.Join(args, ", ")
580+ return data, nil
581+}
582+
583+// scalarDecoder maps a simple argument to its Go type and event decoder.
584+func (s *Scanner) scalarDecoder(arg Arg) (goType, decoder string, err error) {
585+ switch arg.Type {
586+ case "int":
587+ return "int32", "Int32", nil
588+ case "uint":
589+ return "uint32", "Uint32", nil
590+ case "fixed":
591+ return "wl.Fixed", "Fixed", nil
592+ case "string":
593+ return "string", "String", nil
594+ case "array":
595+ return "[]byte", "Array", nil
596+ case "fd":
597+ return "*wl.OwnedFD", "FD", nil
598+ }
599+ return "", "", fmt.Errorf("unsupported event argument type %q", arg.Type)
600+}
601+
602+// goTypeForArg returns the Go type of an object-like argument. Protocol-local
603+// interfaces map to generated types, explicit external references to their
604+// mapped package, and bootstrap display/registry to transport wrappers.
605+func (s *Scanner) goTypeForArg(arg Arg) (string, error) {
606+ switch arg.Type {
607+ case "int":
608+ return "int32", nil
609+ case "uint":
610+ return "uint32", nil
611+ case "fixed":
612+ return "wl.Fixed", nil
613+ case "string":
614+ return "string", nil
615+ case "array":
616+ return "[]byte", nil
617+ case "fd":
618+ return "int", nil
619+ case "object", "new_id":
620+ return s.goTypeForInterface(arg.Interface)
621+ }
622+ return "", fmt.Errorf("unsupported argument type %q", arg.Type)
623+}
624+
625+func (s *Scanner) goTypeForInterface(iface string) (string, error) {
626+ if iface == "" {
627+ return "wl.BaseProxy", nil
628+ }
629+ // The XML package owns its own definitions even when the name starts wl_.
630+ for _, local := range s.protocol.Interfaces {
631+ if local.Name == iface && iface != "wl_display" && iface != "wl_registry" {
632+ return s.toGoName(iface), nil
633+ }
634+ }
635+ if bootstrapTypes[s.toGoName(iface)] {
636+ return "wl." + s.toGoName(iface), nil
637+ }
638+ if _, ok := s.CrossPackage[iface]; ok {
639+ return "cross_" + strings.ReplaceAll(iface, "-", "_") + "." + s.toGoName(iface), nil
640+ }
641+ return "", fmt.Errorf("unresolved interface %q: add CrossPackage mapping", iface)
642+}
643+
644+// paramName turns a protocol argument name into a Go parameter name.
645+func (s *Scanner) paramName(name string, index int) string {
646+ if name == "" {
647+ return fmt.Sprintf("arg%d", index)
648+ }
649+
650+ parts := strings.Split(name, "_")
651+ for i, part := range parts {
652+ if part == "" {
653+ continue
654+ }
655+ if i == 0 {
656+ parts[i] = strings.ToLower(part[:1]) + part[1:]
657+ continue
658+ }
659+ parts[i] = strings.ToUpper(part[:1]) + part[1:]
660+ }
661+ result := strings.Join(parts, "")
662+ if result == "" {
663+ return fmt.Sprintf("arg%d", index)
664+ }
665+ if goKeywords[result] {
666+ return result + "Arg"
667+ }
668+ return result
669+}
670+
671+func (s *Scanner) toGoName(name string) string {
672+ name = strings.TrimPrefix(name, "zwlr_")
673+ name = strings.TrimPrefix(name, "zwp_")
674+ name = strings.TrimPrefix(name, "wl_")
675+ name = strings.TrimSuffix(name, "_v1")
676+ name = strings.TrimSuffix(name, "_v2")
677+
678+ parts := strings.Split(name, "_")
679+ for i, part := range parts {
680+ if part != "" {
681+ parts[i] = strings.ToUpper(part[:1]) + part[1:]
682+ }
683+ }
684+ return strings.Join(parts, "")
685+}
686+
687+// constantName renders an enum entry as the unprefixed constant name.
688+func constantName(enum, entry string) string {
689+ return strings.Join(append(joinName(enum), joinName(entry)...), "_")
690+}
691+
692+// qualifiedConstantName prefixes an enum entry constant with its interface
693+// name. It is used for entries that several interfaces declare, which would
694+// otherwise collide in the generated package.
695+func qualifiedConstantName(iface, enum, entry string) string {
696+ return strings.Join(append(joinName(iface), constantName(enum, entry)), "_")
697+}
698+
699+// joinName upper-cases the underscore-separated parts of a protocol name.
700+func joinName(value string) []string {
701+ parts := strings.Split(value, "_")
702+ out := make([]string, 0, len(parts))
703+ for _, part := range parts {
704+ if part != "" {
705+ out = append(out, strings.ToUpper(part))
706+ }
707+ }
708+ return out
709+}
710+
711+// formatDescription renders an argument or request description as a single
712+// documentation line.
713+func (s *Scanner) formatDescription(desc *Description) string {
714+ if desc == nil {
715+ return ""
716+ }
717+
718+ text := strings.TrimSpace(desc.Text)
719+ if text == "" {
720+ return desc.Summary
721+ }
722+
723+ lines := strings.Split(text, "\n")
724+ formatted := make([]string, 0, len(lines))
725+ for _, line := range lines {
726+ if line = strings.TrimSpace(line); line != "" {
727+ formatted = append(formatted, line)
728+ }
729+ }
730+ return strings.Join(formatted, " ")
731+}
A internal/scanner/scanner_test.go +163 -0
@@ -0,0 +1,163 @@
1+package scanner
2+
3+import (
4+ "os"
5+ "os/exec"
6+ "path/filepath"
7+ "strings"
8+ "testing"
9+)
10+
11+func TestResolution(t *testing.T) {
12+ p := filepath.Join(t.TempDir(), "fixture.xml")
13+ xml := `<protocol name="sample"><interface name="wl_surface" version="1"><event name="done"><arg name="value" type="uint"/></event></interface><interface name="custom_thing" version="1"><request name="make"><arg name="id" type="new_id" interface="wl_surface"/><arg name="output" type="object" interface="wl_output"/></request></interface></protocol>`
14+ if err := os.WriteFile(p, []byte(xml), 0600); err != nil {
15+ t.Fatal(err)
16+ }
17+ s := NewScanner()
18+ if err := s.ParseXML(p); err != nil {
19+ t.Fatal(err)
20+ }
21+ s.CrossPackage = map[string]string{"wl_surface": "github.com/bnema/wlturbo/protocol/core", "wl_output": "github.com/bnema/wlturbo/protocol/core"}
22+ b, err := s.Generate("fixture")
23+ if err != nil {
24+ t.Fatal(err)
25+ }
26+ text := string(b)
27+ if !strings.Contains(text, "(*Surface, error)") || !strings.Contains(text, "output *cross_wl_output.Output") || strings.Contains(text, "cross_wl_surface") {
28+ t.Fatalf("wrong resolution: %s", text)
29+ }
30+}
31+func TestCoreGeneration(t *testing.T) {
32+ s := NewScanner()
33+ if err := s.ParseXML("../../protocol/wayland.xml"); err != nil {
34+ t.Fatal(err)
35+ }
36+ b, err := s.Generate("core")
37+ if err != nil {
38+ t.Fatal(err)
39+ }
40+ for _, want := range []string{"type Keyboard struct", "type Subsurface struct", "type Buffer struct", "type Callback struct", "return \"uint,fd,uint,\", true", "child := &Keyboard{}"} {
41+ if !strings.Contains(string(b), want) {
42+ t.Errorf("missing %s", want)
43+ }
44+ }
45+ if strings.Contains(string(b), "type Display struct") || strings.Contains(string(b), "type Registry struct") {
46+ t.Fatal("bootstrap generated twice")
47+ }
48+}
49+
50+// P3 uses wl_buffer as both an event-created child and a request-created child.
51+// Verify the generated code uses the public constructor instead of embedding
52+// an inaccessible BaseProxy context field across package boundaries.
53+func TestExternalChildFactory(t *testing.T) {
54+ path := filepath.Join(t.TempDir(), "external.xml")
55+ xml := `<protocol name="fixture"><interface name="ext_params" version="1"><request name="create"><arg name="id" type="new_id" interface="wl_buffer"/></request><event name="created"><arg name="id" type="new_id" interface="wl_buffer"/></event></interface></protocol>`
56+ if err := os.WriteFile(path, []byte(xml), 0600); err != nil {
57+ t.Fatal(err)
58+ }
59+ s := NewScanner()
60+ if err := s.ParseXML(path); err != nil {
61+ t.Fatal(err)
62+ }
63+ s.CrossPackage = map[string]string{"wl_buffer": "github.com/bnema/wlturbo/protocol/core"}
64+ generated, err := s.Generate("ext")
65+ if err != nil {
66+ t.Fatal(err)
67+ }
68+ for _, part := range []string{"cross_wl_buffer.NewBuffer(o.Context())", "(*cross_wl_buffer.Buffer, error)", "idObject.SetID(idID)"} {
69+ if !strings.Contains(string(generated), part) {
70+ t.Errorf("generated code missing %q", part)
71+ }
72+ }
73+ if strings.Contains(string(generated), "&cross_wl_buffer.Buffer{}") {
74+ t.Fatal("foreign child was constructed without its package factory")
75+ }
76+}
77+
78+func TestUnresolvedExternalInterface(t *testing.T) {
79+ path := filepath.Join(t.TempDir(), "extension.xml")
80+ xml := `<protocol name="ext"><interface name="ext_parent" version="1"><request name="make"><arg name="id" type="new_id" interface="wl_surface"/></request></interface></protocol>`
81+ if err := os.WriteFile(path, []byte(xml), 0600); err != nil {
82+ t.Fatal(err)
83+ }
84+ s := NewScanner()
85+ if err := s.ParseXML(path); err != nil {
86+ t.Fatal(err)
87+ }
88+ if _, err := s.Generate("ext"); err == nil || !strings.Contains(err.Error(), "wl_surface") {
89+ t.Fatalf("unmapped external reference: %v", err)
90+ }
91+}
92+
93+func TestMappedExtensionCompilesAndDispatchesChild(t *testing.T) {
94+ goBin, err := exec.LookPath("go")
95+ if err != nil {
96+ t.Skip("go unavailable: generated extension compilation requires go")
97+ }
98+ root, err := filepath.Abs("../..")
99+ if err != nil {
100+ t.Fatal(err)
101+ }
102+ dir := t.TempDir()
103+ xml := `<protocol name="ext"><interface name="ext_parent" version="1"><event name="child"><arg name="id" type="new_id" interface="wl_buffer"/></event></interface></protocol>`
104+ path := filepath.Join(dir, "ext.xml")
105+ if err := os.WriteFile(path, []byte(xml), 0600); err != nil {
106+ t.Fatal(err)
107+ }
108+ s := NewScanner()
109+ if err := s.ParseXML(path); err != nil {
110+ t.Fatal(err)
111+ }
112+ s.CrossPackage = map[string]string{"wl_buffer": "github.com/bnema/wlturbo/protocol/core"}
113+ generated, err := s.Generate("ext")
114+ if err != nil {
115+ t.Fatal(err)
116+ }
117+ if err := os.WriteFile(filepath.Join(dir, "ext.go"), generated, 0600); err != nil {
118+ t.Fatal(err)
119+ }
120+ mod := "module example.com/ext\n\ngo 1.27\n\nrequire github.com/bnema/wlturbo v0.0.0\nreplace github.com/bnema/wlturbo => " + root + "\n"
121+ if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(mod), 0600); err != nil {
122+ t.Fatal(err)
123+ }
124+ test := `package ext
125+import (
126+ "encoding/binary"
127+ "net"
128+ "os"
129+ "testing"
130+ "github.com/bnema/wlturbo"
131+ "github.com/bnema/wlturbo/protocol/core"
132+ "golang.org/x/sys/unix"
133+)
134+func TestTypedChild(t *testing.T) {
135+ f, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0); if err != nil { t.Fatal(err) }
136+ a, b := os.NewFile(uintptr(f[0]), "a"), os.NewFile(uintptr(f[1]), "b")
137+ c, err := net.FileConn(a); a.Close(); if err != nil { t.Fatal(err) }; defer c.Close()
138+ p, err := net.FileConn(b); b.Close(); if err != nil { t.Fatal(err) }; defer p.Close()
139+ d, err := wlturbo.ConnectFromConn(c); if err != nil { t.Fatal(err) }; defer d.Close()
140+ parent := NewExtParent(d.Context()); parent.SetID(d.AllocateID()); d.Context().Register(parent)
141+ var child *core.Buffer
142+ parent.OnChild(func(b *core.Buffer) { child = b })
143+ frame := make([]byte, 12); binary.LittleEndian.PutUint32(frame, parent.ID()); binary.LittleEndian.PutUint32(frame[4:], 12<<16); binary.LittleEndian.PutUint32(frame[8:], 0xff000001)
144+ if _, err := p.Write(frame); err != nil { t.Fatal(err) }
145+ if err := d.Dispatch(); err != nil { t.Fatal(err) }
146+ if child == nil || child.ID() != 0xff000001 { t.Fatalf("child: %v", child) }
147+ called := false; child.OnRelease(func(){ called = true })
148+ frame = frame[:8]; binary.LittleEndian.PutUint32(frame, child.ID()); binary.LittleEndian.PutUint32(frame[4:], 8<<16)
149+ if _, err := p.Write(frame); err != nil { t.Fatal(err) }
150+ if err := d.Dispatch(); err != nil { t.Fatal(err) }
151+ if !called { t.Fatal("typed child did not dispatch release") }
152+}
153+`
154+ if err := os.WriteFile(filepath.Join(dir, "ext_test.go"), []byte(test), 0600); err != nil {
155+ t.Fatal(err)
156+ }
157+ cmd := exec.Command(goBin, "test", "./...")
158+ cmd.Dir = dir
159+ cmd.Env = append(os.Environ(), "GOWORK=off", "GOFLAGS=-mod=mod")
160+ if out, err := cmd.CombinedOutput(); err != nil {
161+ t.Fatalf("generated extension test: %v\n%s", err, out)
162+ }
163+}
A internal/scanner/template.go +142 -0
@@ -0,0 +1,142 @@
1+package scanner
2+
3+// protocolTemplate is the template for generating WLTurbo-backed Go bindings.
4+//
5+// Generated objects embed wl.BaseProxy, allocate and register child objects
6+// through the wl.Context, send requests through that context, and dispatch
7+// typed events to registered handlers. Nothing in the generated code opens a
8+// socket or frames messages itself: that is the transport's job.
9+const protocolTemplate = `// Code generated by wlturbo-scanner. DO NOT EDIT.
10+// Protocol: {{.Protocol}}
11+// Source: {{.Source}}
12+
13+package {{.Package}}
14+
15+{{.ImportBlock}}
16+
17+{{range .Constants}}
18+// {{.Name}} is the {{.Enum}} {{.Entry}} value.
19+const {{.Name}}{{if .Type}} {{.Type}}{{end}} = {{.Value}}
20+{{- end}}
21+{{range .Interfaces}}
22+{{- $iface := .}}
23+
24+// {{$iface.GoName}}Interface is the protocol interface name.
25+const {{$iface.GoName}}Interface = "{{$iface.Name}}"
26+
27+// {{$iface.GoName}} is the generated binding for {{$iface.Name}} version {{$iface.Version}}.
28+type {{$iface.GoName}} struct {
29+ wl.BaseProxy
30+{{- if $iface.HasEvents}}
31+ // handlersMu guards the handler slices: a client may register or replace
32+ // handlers while another goroutine dispatches events.
33+ handlersMu sync.Mutex
34+{{- end}}
35+{{- range $iface.Events}}
36+ {{.HandlerField}} []func({{.HandlerParams}})
37+{{- end}}
38+}
39+
40+// New{{$iface.GoName}} returns an unbound {{$iface.GoName}}.
41+func New{{$iface.GoName}}(ctx *wl.Context) *{{$iface.GoName}} {
42+ obj := &{{$iface.GoName}}{}
43+ obj.SetContext(ctx)
44+ ctx.Register(obj)
45+ return obj
46+}
47+{{- range $iface.Requests}}
48+
49+{{- if .Doc}}
50+// {{.Doc}}
51+{{- end}}
52+func (o *{{$iface.GoName}}) {{.GoName}}({{.Params}}) {{.Results}} {
53+if err := o.Context().CheckProxy(o); err != nil { return {{.ErrorReturn}} }
54+{{- range .ArgPreparations}}
55+ {{.}}
56+{{- end}}
57+{{- if .CreatesChild}}
58+{{- if .ChildFactory}}
59+ {{.ChildVar}} := {{.ChildFactory}}(o.Context())
60+{{- else}}
61+ {{.ChildVar}} := &{{.ChildType}}{}
62+ {{.ChildVar}}.SetContext(o.Context())
63+{{- end}}
64+ {{.ChildVar}}.SetID(o.Context().AllocateID())
65+ o.Context().Register({{.ChildVar}})
66+{{- end}}
67+ if err := o.Context().{{.SendCall}}; err != nil {
68+{{- if .CreatesChild}}
69+ o.Context().Unregister({{.ChildVar}})
70+{{- end}}
71+ return {{.ErrorReturn}}
72+ }
73+{{- range .FDArgs}}
74+ _ = wl.CloseSentFD({{.}})
75+{{- end}}
76+{{- if .CreatesChild}}
77+ return {{.ChildVar}}, nil
78+{{- else}}
79+ return nil
80+{{- end}}
81+}
82+{{- end}}
83+{{- range $iface.Events}}
84+
85+// On{{.GoName}} registers a handler for the {{.Name}} event.
86+//
87+// Handlers are appended, so a second call adds another handler rather than
88+// replacing the first. Registration is safe while another goroutine dispatches.
89+func (o *{{$iface.GoName}}) On{{.GoName}}(handler func({{.HandlerParams}})) {
90+ if handler == nil { return }
91+ o.handlersMu.Lock()
92+ defer o.handlersMu.Unlock()
93+ o.{{.HandlerField}} = append(o.{{.HandlerField}}, handler)
94+}
95+
96+// {{.HandlerAccessor}} returns the handlers registered for {{.Name}}, taken
97+// under the lock so a handler may register another handler while events are
98+// being dispatched.
99+func (o *{{$iface.GoName}}) {{.HandlerAccessor}}() []func({{.HandlerParams}}) {
100+ o.handlersMu.Lock()
101+ defer o.handlersMu.Unlock()
102+ return o.{{.HandlerField}}
103+}
104+{{- end}}
105+
106+// EventSignature returns the declared event argument types in wire order.
107+func (o *{{$iface.GoName}}) EventSignature(opcode uint16) (string, bool) {
108+ switch opcode {
109+ {{- range $iface.SignatureCases}}
110+ case {{.Opcode}}: return "{{.Signature}}", true
111+ {{- end}}
112+ default: return "", false
113+ }
114+}
115+
116+// Dispatch decodes one event on {{$iface.Name}} and calls the registered handlers.
117+func (o *{{$iface.GoName}}) Dispatch(event *wl.Event) {
118+ switch event.Opcode {
119+{{- range $iface.Events}}
120+ case {{.Opcode}}: // {{.Name}}
121+{{- range .DecodeLines}}
122+ {{.}}
123+{{- end}}
124+ {{if .HasFD}}{{range .FDNames}}owned{{.}} := {{.}}; defer owned{{.}}.Close()
125+ {{end}}{{end}}
126+ for i, handler := range o.{{.HandlerAccessor}}() {
127+ {{- if .HasFD}}
128+ if i > 0 { {{range .FDNames}}{{.}} = nil; {{end}} }
129+ {{- else}}
130+ _ = i
131+ {{- end}}
132+ handler({{.HandlerArgs}})
133+ }
134+{{- if .ServerDestroy}}
135+ o.Context().Unregister(o)
136+{{- end}}
137+{{- end}}
138+ default:
139+ }
140+}
141+{{end}}
142+`
A internal/scanner/testdata/protocol_fixture.xml +112 -0
@@ -0,0 +1,112 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<protocol name="wlvision_fixture">
3+ <copyright>
4+ Synthetic protocol used to test the wlvision protocol generator. It exists
5+ only in this repository's tests.
6+ </copyright>
7+
8+ <interface name="zwlr_fixture_manager_v1" version="2">
9+ <description summary="fixture manager">
10+ Exercises new_id requests, object references, fixed point numbers,
11+ strings, arrays, file descriptors and destructors.
12+ </description>
13+
14+ <request name="create_thing">
15+ <description summary="create a fixture object"/>
16+ <arg name="seat" type="object" interface="wl_seat"/>
17+ <arg name="id" type="new_id" interface="zwlr_fixture_thing_v1"/>
18+ </request>
19+
20+ <request name="set_ratio">
21+ <arg name="ratio" type="fixed"/>
22+ </request>
23+
24+ <request name="set_label">
25+ <arg name="label" type="string"/>
26+ </request>
27+
28+ <request name="set_region">
29+ <arg name="region" type="object" interface="wl_region"/>
30+ </request>
31+
32+ <request name="set_buffer">
33+ <description summary="reference an interface without a typed wrapper"/>
34+ <arg name="buffer" type="object" interface="wl_buffer"/>
35+ </request>
36+
37+ <request name="set_optional_output">
38+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
39+ </request>
40+
41+ <request name="attach_raw">
42+ <arg name="data" type="array"/>
43+ </request>
44+
45+ <request name="pass_fd">
46+ <arg name="fd" type="fd"/>
47+ </request>
48+
49+ <request name="destroy" type="destructor">
50+ <description summary="destroy the manager"/>
51+ </request>
52+
53+ <request name="create_labeled_thing">
54+ <description summary="new_id argument that is not the last one"/>
55+ <arg name="id" type="new_id" interface="zwlr_fixture_thing_v1"/>
56+ <arg name="label" type="string"/>
57+ <arg name="ratio" type="fixed"/>
58+ </request>
59+ </interface>
60+
61+ <interface name="zwlr_fixture_thing_v1" version="1">
62+ <description summary="fixture object"/>
63+
64+ <enum name="fixture_state">
65+ <entry name="idle" value="0"/>
66+ <entry name="busy" value="1"/>
67+ </enum>
68+
69+ <request name="set_scale">
70+ <arg name="scale" type="fixed"/>
71+ </request>
72+
73+ <request name="destroy" type="destructor"/>
74+
75+ <event name="ready">
76+ <arg name="serial" type="uint"/>
77+ </event>
78+
79+ <event name="label_changed">
80+ <arg name="label" type="string"/>
81+ </event>
82+
83+ <event name="geometry">
84+ <arg name="x" type="int"/>
85+ <arg name="y" type="int"/>
86+ <arg name="width" type="uint"/>
87+ <arg name="height" type="uint"/>
88+ </event>
89+
90+ <event name="mode">
91+ <arg name="mode" type="new_id" interface="zwlr_fixture_mode_v1"/>
92+ </event>
93+
94+ <event name="peer">
95+ <arg name="surface" type="object" interface="wl_surface"/>
96+ </event>
97+
98+ <event name="scaled">
99+ <arg name="scale" type="fixed"/>
100+ </event>
101+
102+ <event name="blob">
103+ <arg name="data" type="array"/>
104+ </event>
105+ </interface>
106+
107+ <interface name="zwlr_fixture_mode_v1" version="1">
108+ <description summary="fixture mode object"/>
109+
110+ <request name="destroy" type="destructor"/>
111+ </interface>
112+</protocol>
A protocol/SOURCE.md +7 -0
@@ -0,0 +1,7 @@
1+# Core protocol source
2+
3+`wayland.xml` is from Wayland 1.26.0, upstream GitLab
4+`https://gitlab.freedesktop.org/wayland/wayland`, tag `1.26.0`, peeled commit
5+`87cc8a8728a923fc57938faa81ba0e74f34ecdc7`.
6+SHA-256: `cc860987e54f8d85c940e97fa1270c69b6e4ad31fbcf5a7f00107ce1157f5e07`.
7+License: MIT (full copyright and grant in `wayland.xml` `<copyright>`).
A protocol/core/core_test.go +532 -0
@@ -0,0 +1,532 @@
1+//go:build linux
2+
3+package core_test
4+
5+import (
6+ "encoding/binary"
7+ "errors"
8+ "io"
9+ "net"
10+ "os"
11+ "testing"
12+ "time"
13+
14+ "github.com/bnema/wlturbo"
15+ "github.com/bnema/wlturbo/protocol/core"
16+ "golang.org/x/sys/unix"
17+)
18+
19+func pair(t *testing.T) (*net.UnixConn, *net.UnixConn) {
20+ t.Helper()
21+ f, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
22+ if err != nil {
23+ t.Fatal(err)
24+ }
25+ a := os.NewFile(uintptr(f[0]), "a")
26+ b := os.NewFile(uintptr(f[1]), "b")
27+ ca, e := net.FileConn(a)
28+ a.Close()
29+ if e != nil {
30+ t.Fatal(e)
31+ }
32+ cb, e := net.FileConn(b)
33+ b.Close()
34+ if e != nil {
35+ t.Fatal(e)
36+ }
37+ t.Cleanup(func() { ca.Close(); cb.Close() })
38+ return ca.(*net.UnixConn), cb.(*net.UnixConn)
39+}
40+func frame(id uint32, op uint16, body ...uint32) []byte {
41+ p := make([]byte, 8+4*len(body))
42+ binary.LittleEndian.PutUint32(p, id)
43+ binary.LittleEndian.PutUint32(p[4:], uint32(len(p))<<16|uint32(op))
44+ for i, v := range body {
45+ binary.LittleEndian.PutUint32(p[8+i*4:], v)
46+ }
47+ return p
48+}
49+func receiveRequest(t *testing.T, p *net.UnixConn) {
50+ t.Helper()
51+ p.SetReadDeadline(testDeadline())
52+ b := make([]byte, 256)
53+ if _, e := p.Read(b); e != nil {
54+ t.Fatal(e)
55+ }
56+}
57+func testDeadline() (d time.Time) { return time.Now().Add(2 * time.Second) }
58+func send(t *testing.T, p *net.UnixConn, msg []byte, fd ...int) {
59+ t.Helper()
60+ var oob []byte
61+ if len(fd) > 0 {
62+ oob = unix.UnixRights(fd...)
63+ }
64+ if _, _, e := p.WriteMsgUnix(msg, oob, nil); e != nil {
65+ t.Fatal(e)
66+ }
67+}
68+func TestGeneratedCoreOverSocketpair(t *testing.T) {
69+ c, p := pair(t)
70+ d, err := wlturbo.ConnectFromConn(c)
71+ if err != nil {
72+ t.Fatal(err)
73+ }
74+ defer d.Close()
75+ receiveRequest(t, p) // get_registry
76+ seat := core.NewSeat(d.Context())
77+ if err := d.Registry().Bind(1, core.SeatInterface, 1, seat); err != nil {
78+ t.Fatal(err)
79+ }
80+ receiveRequest(t, p)
81+ kb, err := seat.GetKeyboard()
82+ if err != nil {
83+ t.Fatal(err)
84+ }
85+ receiveRequest(t, p)
86+ var key uint32
87+ kb.OnKey(func(_, _, k, _ uint32) { key = k })
88+ send(t, p, frame(kb.ID(), 3, 1, 2, 42, 1))
89+ if err := d.Dispatch(); err != nil {
90+ t.Fatal(err)
91+ }
92+ if key != 42 {
93+ t.Fatalf("key=%d", key)
94+ }
95+ compositor := core.NewCompositor(d.Context())
96+ if err := d.Registry().Bind(2, core.CompositorInterface, 1, compositor); err != nil {
97+ t.Fatal(err)
98+ }
99+ receiveRequest(t, p)
100+ surface, err := compositor.CreateSurface()
101+ if err != nil {
102+ t.Fatal(err)
103+ }
104+ receiveRequest(t, p)
105+ callback, err := surface.Frame()
106+ if err != nil {
107+ t.Fatal(err)
108+ }
109+ receiveRequest(t, p)
110+ var done uint32
111+ callback.OnDone(func(ms uint32) { done = ms })
112+ send(t, p, frame(callback.ID(), 0, 123))
113+ if err := d.Dispatch(); err != nil {
114+ t.Fatal(err)
115+ }
116+ if done != 123 {
117+ t.Fatalf("done=%d", done)
118+ }
119+ send(t, p, frame(callback.ID(), 0, 124))
120+ if err := d.Dispatch(); !errors.Is(err, wlturbo.ErrUnknownObject) {
121+ t.Fatalf("destroyed callback: %v", err)
122+ }
123+}
124+func TestKeymapFDNoHandlerAndMultipleHandlers(t *testing.T) {
125+ c, p := pair(t)
126+ d, e := wlturbo.ConnectFromConn(c)
127+ if e != nil {
128+ t.Fatal(e)
129+ }
130+ defer d.Close()
131+ receiveRequest(t, p)
132+ kb := core.NewKeyboard(d.Context())
133+ kb.SetID(d.AllocateID())
134+ d.Context().Register(kb)
135+ r, w, e := os.Pipe()
136+ if e != nil {
137+ t.Fatal(e)
138+ }
139+ defer r.Close()
140+ send(t, p, frame(kb.ID(), 0, 1, 8), int(w.Fd()))
141+ w.Close()
142+ if e := d.Dispatch(); e != nil {
143+ t.Fatal(e)
144+ }
145+ buf := make([]byte, 1)
146+ if n, e := r.Read(buf); n != 0 || e != io.EOF {
147+ t.Fatalf("unclaimed FD n=%d err=%v", n, e)
148+ }
149+ r2, w2, e := os.Pipe()
150+ if e != nil {
151+ t.Fatal(e)
152+ }
153+ defer r2.Close()
154+ received := 0
155+ kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) {
156+ if fd == nil {
157+ t.Fatal("first FD missing")
158+ }
159+ n, e := fd.Take()
160+ if e != nil {
161+ t.Fatal(e)
162+ }
163+ received++
164+ unix.Close(n)
165+ })
166+ kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) {
167+ if fd != nil {
168+ t.Fatal("duplicated owner")
169+ }
170+ received++
171+ })
172+ send(t, p, frame(kb.ID(), 0, 1, 8), int(w2.Fd()))
173+ w2.Close()
174+ if e := d.Dispatch(); e != nil {
175+ t.Fatal(e)
176+ }
177+ if received != 2 {
178+ t.Fatalf("handlers=%d", received)
179+ }
180+}
181+func TestBufferReleaseAndOfferChild(t *testing.T) {
182+ c, p := pair(t)
183+ d, e := wlturbo.ConnectFromConn(c)
184+ if e != nil {
185+ t.Fatal(e)
186+ }
187+ defer d.Close()
188+ receiveRequest(t, p)
189+ b := core.NewBuffer(d.Context())
190+ b.SetID(d.AllocateID())
191+ d.Context().Register(b)
192+ releases := 0
193+ b.OnRelease(func() { releases++ })
194+ send(t, p, frame(b.ID(), 0))
195+ if e := d.Dispatch(); e != nil {
196+ t.Fatal(e)
197+ }
198+ if releases != 1 {
199+ t.Fatal("release not dispatched")
200+ }
201+ device := core.NewDataDevice(d.Context())
202+ device.SetID(d.AllocateID())
203+ d.Context().Register(device)
204+ var offer *core.DataOffer
205+ device.OnDataOffer(func(o *core.DataOffer) { offer = o })
206+ send(t, p, frame(device.ID(), 0, 0xff000001))
207+ if e := d.Dispatch(); e != nil {
208+ t.Fatal(e)
209+ }
210+ if offer == nil || offer.ID() != 0xff000001 {
211+ t.Fatalf("offer=%v", offer)
212+ }
213+}
214+
215+func TestMissingKeymapDescriptor(t *testing.T) {
216+ c, p := pair(t)
217+ d, e := wlturbo.ConnectFromConn(c)
218+ if e != nil {
219+ t.Fatal(e)
220+ }
221+ defer d.Close()
222+ receiveRequest(t, p)
223+ kb := core.NewKeyboard(d.Context())
224+ kb.SetID(d.AllocateID())
225+ d.Context().Register(kb)
226+ send(t, p, frame(kb.ID(), 0, 1, 8))
227+ if e := d.Dispatch(); !errors.Is(e, wlturbo.ErrMalformedFrame) {
228+ t.Fatalf("missing FD: %v", e)
229+ }
230+}
231+
232+func TestInterleavedFDAndPlainMessages(t *testing.T) {
233+ c, p := pair(t)
234+ d, e := wlturbo.ConnectFromConn(c)
235+ if e != nil {
236+ t.Fatal(e)
237+ }
238+ defer d.Close()
239+ receiveRequest(t, p)
240+ kb := core.NewKeyboard(d.Context())
241+ kb.SetID(d.AllocateID())
242+ d.Context().Register(kb)
243+ calls := 0
244+ kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) {
245+ if fd == nil {
246+ t.Fatal("FD not assigned")
247+ }
248+ _ = fd.Close()
249+ calls++
250+ })
251+ kb.OnKey(func(_, _, key, _ uint32) {
252+ if key != 33 {
253+ t.Errorf("key=%d", key)
254+ }
255+ calls++
256+ })
257+ r, w, e := os.Pipe()
258+ if e != nil {
259+ t.Fatal(e)
260+ }
261+ defer r.Close()
262+ send(t, p, frame(kb.ID(), 0, 1, 8), int(w.Fd()))
263+ w.Close()
264+ send(t, p, frame(kb.ID(), 3, 1, 2, 33, 1))
265+ if e := d.Dispatch(); e != nil {
266+ t.Fatal(e)
267+ }
268+ if e := d.Dispatch(); e != nil {
269+ t.Fatal(e)
270+ }
271+ if calls != 2 {
272+ t.Fatalf("calls=%d", calls)
273+ }
274+}
275+func TestMalformedGeneratedPayload(t *testing.T) {
276+ c, p := pair(t)
277+ d, e := wlturbo.ConnectFromConn(c)
278+ if e != nil {
279+ t.Fatal(e)
280+ }
281+ defer d.Close()
282+ receiveRequest(t, p)
283+ kb := core.NewKeyboard(d.Context())
284+ kb.SetID(d.AllocateID())
285+ d.Context().Register(kb)
286+ send(t, p, frame(kb.ID(), 3, 1, 2))
287+ if e := d.Dispatch(); !errors.Is(e, wlturbo.ErrMalformedFrame) {
288+ t.Fatalf("truncated key: %v", e)
289+ }
290+}
291+
292+func TestServerChildEventsAfterOffer(t *testing.T) {
293+ c, p := pair(t)
294+ d, e := wlturbo.ConnectFromConn(c)
295+ if e != nil {
296+ t.Fatal(e)
297+ }
298+ defer d.Close()
299+ receiveRequest(t, p)
300+ device := core.NewDataDevice(d.Context())
301+ device.SetID(d.AllocateID())
302+ d.Context().Register(device)
303+ const offerID = 0xff000011
304+ var offer *core.DataOffer
305+ device.OnDataOffer(func(o *core.DataOffer) { offer = o })
306+ send(t, p, frame(device.ID(), 0, offerID))
307+ if e := d.Dispatch(); e != nil {
308+ t.Fatal(e)
309+ }
310+ if offer == nil {
311+ t.Fatal("missing concrete child")
312+ }
313+ got := ""
314+ offer.OnOffer(func(mime string) { got = mime })
315+ payload := append(frame(offerID, 0), 4, 0, 0, 0, 'a', 'b', 'c', 0)
316+ binary.LittleEndian.PutUint32(payload[4:], uint32(len(payload))<<16)
317+ send(t, p, payload)
318+ if e := d.Dispatch(); e != nil {
319+ t.Fatal(e)
320+ }
321+ if got != "abc" {
322+ t.Fatalf("typed child event: %q", got)
323+ }
324+}
325+
326+func TestRegistryBootstrapAnnouncements(t *testing.T) {
327+ c, p := pair(t)
328+ d, e := wlturbo.ConnectFromConn(c)
329+ if e != nil {
330+ t.Fatal(e)
331+ }
332+ defer d.Close()
333+ receiveRequest(t, p)
334+ var got wlturbo.Global
335+ d.Registry().AddHandler("wl_compositor", func(r *wlturbo.Registry, n, v uint32) { got, _ = r.FindGlobalByName(n) })
336+ name := "wl_compositor"
337+ body := make([]byte, 4+4+((len(name)+1+3)&^3)+4)
338+ binary.LittleEndian.PutUint32(body, 17)
339+ binary.LittleEndian.PutUint32(body[4:], uint32(len(name)+1))
340+ copy(body[8:], name)
341+ binary.LittleEndian.PutUint32(body[len(body)-4:], 7)
342+ msg := append(frame(d.Registry().ID(), 0), body...)
343+ binary.LittleEndian.PutUint32(msg[4:], uint32(len(msg))<<16)
344+ send(t, p, msg)
345+ if e := d.Dispatch(); e != nil {
346+ t.Fatal(e)
347+ }
348+ if got.Name != 17 || got.Interface != name || got.Version != 7 {
349+ t.Fatalf("global: %+v", got)
350+ }
351+ send(t, p, frame(d.Registry().ID(), 1, 17))
352+ if e := d.Dispatch(); e != nil {
353+ t.Fatal(e)
354+ }
355+ if _, ok := d.Registry().FindGlobalByName(17); ok {
356+ t.Fatal("global not removed")
357+ }
358+ send(t, p, frame(d.Registry().ID(), 2))
359+ if e := d.Dispatch(); !errors.Is(e, wlturbo.ErrUnknownOpcode) {
360+ t.Fatalf("unknown registry opcode: %v", e)
361+ }
362+}
363+func TestUnclaimedFirstHandlerFDClosed(t *testing.T) {
364+ c, p := pair(t)
365+ d, e := wlturbo.ConnectFromConn(c)
366+ if e != nil {
367+ t.Fatal(e)
368+ }
369+ defer d.Close()
370+ receiveRequest(t, p)
371+ kb := core.NewKeyboard(d.Context())
372+ kb.SetID(d.AllocateID())
373+ d.Context().Register(kb)
374+ kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) {
375+ if fd == nil {
376+ t.Fatal("missing first owner")
377+ }
378+ })
379+ kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) {
380+ if fd != nil {
381+ t.Fatal("duplicate owner")
382+ }
383+ })
384+ r, w, e := os.Pipe()
385+ if e != nil {
386+ t.Fatal(e)
387+ }
388+ defer r.Close()
389+ send(t, p, frame(kb.ID(), 0, 1, 8), int(w.Fd()))
390+ w.Close()
391+ if e := d.Dispatch(); e != nil {
392+ t.Fatal(e)
393+ }
394+ if e := r.SetReadDeadline(testDeadline()); e != nil {
395+ t.Fatal(e)
396+ }
397+ var b [1]byte
398+ if n, e := r.Read(b[:]); n != 0 || e != io.EOF {
399+ t.Fatalf("unclaimed FD not closed: %d %v", n, e)
400+ }
401+}
402+
403+func TestDestroyedProxyRejectsRequests(t *testing.T) {
404+ c, p := pair(t)
405+ d, err := wlturbo.ConnectFromConn(c)
406+ if err != nil {
407+ t.Fatal(err)
408+ }
409+ defer d.Close()
410+ receiveRequest(t, p)
411+ surface := core.NewSurface(d.Context())
412+ surface.SetID(d.AllocateID())
413+ d.Context().Register(surface)
414+ if err := surface.Destroy(); err != nil {
415+ t.Fatal(err)
416+ }
417+ receiveRequest(t, p)
418+ if err := surface.Destroy(); err == nil {
419+ t.Fatal("second destroy succeeded")
420+ }
421+ if err := surface.Commit(); err == nil {
422+ t.Fatal("request on destroyed surface succeeded")
423+ }
424+ if _, err := surface.Frame(); err == nil {
425+ t.Fatal("child request on destroyed surface succeeded")
426+ }
427+ p.SetReadDeadline(time.Now().Add(20 * time.Millisecond))
428+ _, err = p.Read(make([]byte, 32))
429+ ne, ok := err.(net.Error)
430+ if !ok || !ne.Timeout() {
431+ t.Fatalf("unexpected bytes on wire: %v", err)
432+ }
433+}
434+
435+func TestConcurrentDestroySendsOnce(t *testing.T) {
436+ c, p := pair(t)
437+ d, err := wlturbo.ConnectFromConn(c)
438+ if err != nil {
439+ t.Fatal(err)
440+ }
441+ defer d.Close()
442+ receiveRequest(t, p)
443+ const rounds, destroyers, committers = 100, 4, 4
444+ totalCommits := 0
445+ for round := 0; round < rounds; round++ {
446+ surface := core.NewSurface(d.Context())
447+ surface.SetID(d.AllocateID())
448+ d.Context().Register(surface)
449+ start := make(chan struct{})
450+ destroyed := make(chan error, destroyers)
451+ committed := make(chan error, committers)
452+ for i := 0; i < destroyers; i++ {
453+ go func() { <-start; destroyed <- surface.Destroy() }()
454+ }
455+ for i := 0; i < committers; i++ {
456+ go func() { <-start; committed <- surface.Commit() }()
457+ }
458+ close(start)
459+ okDestroy, okCommit := 0, 0
460+ for i := 0; i < destroyers; i++ {
461+ if <-destroyed == nil {
462+ okDestroy++
463+ }
464+ }
465+ for i := 0; i < committers; i++ {
466+ if <-committed == nil {
467+ okCommit++
468+ }
469+ }
470+ if okDestroy != 1 {
471+ t.Fatalf("round %d: %d destroys succeeded, want 1", round, okDestroy)
472+ }
473+ totalCommits += okCommit
474+ // Decode every frame: exactly okCommit commits (opcode 6), then one
475+ // destroy (opcode 0) for this surface, and nothing after it.
476+ frames := readFrames(t, p, okCommit+1)
477+ for i, f := range frames {
478+ if f.id != surface.ID() {
479+ t.Fatalf("round %d: frame %d targets object %d", round, i, f.id)
480+ }
481+ last := i == len(frames)-1
482+ if last && f.opcode != 0 || !last && f.opcode != 6 {
483+ t.Fatalf("round %d: frame %d opcode %d (last=%v)", round, i, f.opcode, last)
484+ }
485+ }
486+ p.SetReadDeadline(time.Now().Add(2 * time.Millisecond))
487+ if n, err := p.Read(make([]byte, 8)); n != 0 {
488+ t.Fatalf("round %d: request after destroy (%d bytes, %v)", round, n, err)
489+ }
490+ p.SetReadDeadline(time.Time{})
491+ }
492+ // Ordering is only exercised when some commits won the race.
493+ if totalCommits == 0 {
494+ t.Fatal("no commit succeeded in any round; ordering was not exercised")
495+ }
496+}
497+
498+type wireFrame struct {
499+ id uint32
500+ opcode uint16
501+}
502+
503+func readFrames(t *testing.T, p *net.UnixConn, n int) []wireFrame {
504+ t.Helper()
505+ p.SetReadDeadline(testDeadline())
506+ defer p.SetReadDeadline(time.Time{})
507+ var out []wireFrame
508+ var buf []byte
509+ tmp := make([]byte, 4096)
510+ for len(out) < n {
511+ for len(buf) >= 8 {
512+ size := int(binary.LittleEndian.Uint32(buf[4:8]) >> 16)
513+ if size < 8 || len(buf) < size {
514+ break
515+ }
516+ out = append(out, wireFrame{binary.LittleEndian.Uint32(buf[0:4]), uint16(binary.LittleEndian.Uint32(buf[4:8]))})
517+ buf = buf[size:]
518+ }
519+ if len(out) >= n {
520+ break
521+ }
522+ m, err := p.Read(tmp)
523+ if err != nil {
524+ t.Fatalf("read frames: got %d of %d: %v", len(out), n, err)
525+ }
526+ buf = append(buf, tmp[:m]...)
527+ }
528+ if len(out) != n || len(buf) != 0 {
529+ t.Fatalf("got %d frames (+%d bytes), want %d", len(out), len(buf), n)
530+ }
531+ return out
532+}
A protocol/core/doc.go +8 -0
@@ -0,0 +1,8 @@
1+// Package core contains canonical Wayland core protocol bindings generated from
2+// pinned wayland.xml. Bind globals with display.Registry().Bind and the
3+// corresponding New* constructor. Client-created and server-created children
4+// are registered with their concrete generated types; never replace them with
5+// transport wrappers. On* handlers receive typed values. Received OwnedFDs
6+// are closed after the event unless a handler calls Take; request FDs are
7+// transferred (and closed locally) only after a complete successful send.
8+package core
A protocol/core/wayland_generated.go +4216 -0 large diff (4216 lines changed): collapsed
A protocol/generate.go +3 -0
@@ -0,0 +1,3 @@
1+package protocol
2+
3+//go:generate go run ../cmd/wlturbo-scanner -p core -o core/wayland_generated.go wayland.xml
A protocol/wayland.xml +3442 -0
@@ -0,0 +1,3442 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<protocol name="wayland">
3+
4+ <copyright>
5+ Copyright © 2008-2011 Kristian Høgsberg
6+ Copyright © 2010-2011 Intel Corporation
7+ Copyright © 2012-2013 Collabora, Ltd.
8+
9+ Permission is hereby granted, free of charge, to any person
10+ obtaining a copy of this software and associated documentation files
11+ (the "Software"), to deal in the Software without restriction,
12+ including without limitation the rights to use, copy, modify, merge,
13+ publish, distribute, sublicense, and/or sell copies of the Software,
14+ and to permit persons to whom the Software is furnished to do so,
15+ subject to the following conditions:
16+
17+ The above copyright notice and this permission notice (including the
18+ next paragraph) shall be included in all copies or substantial
19+ portions of the Software.
20+
21+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
25+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
26+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
27+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28+ SOFTWARE.
29+ </copyright>
30+
31+ <interface name="wl_display" version="1">
32+ <description summary="core global object">
33+ The core global object. This is a special singleton object. It
34+ is used for internal Wayland protocol features.
35+ </description>
36+
37+ <request name="sync">
38+ <description summary="asynchronous roundtrip">
39+ The sync request asks the server to emit the 'done' event
40+ on the returned wl_callback object. Since requests are
41+ handled in-order and events are delivered in-order, this can
42+ be used as a barrier to ensure all previous requests and the
43+ resulting events have been handled.
44+
45+ The object returned by this request will be destroyed by the
46+ compositor after the callback is fired and as such the client must not
47+ attempt to use it after that point.
48+
49+ The callback_data passed in the callback is undefined and should be ignored.
50+ </description>
51+ <arg name="callback" type="new_id" interface="wl_callback"
52+ summary="callback object for the sync request"/>
53+ </request>
54+
55+ <request name="get_registry">
56+ <description summary="get global registry object">
57+ This request creates a registry object that allows the client
58+ to list and bind the global objects available from the
59+ compositor.
60+
61+ It should be noted that the server side resources consumed in
62+ response to a get_registry request can only be released when the
63+ client disconnects, not when the client side proxy is destroyed.
64+ Therefore, clients should invoke get_registry as infrequently as
65+ possible to avoid wasting memory.
66+ </description>
67+ <arg name="registry" type="new_id" interface="wl_registry"
68+ summary="global registry object"/>
69+ </request>
70+
71+ <event name="error">
72+ <description summary="fatal error event">
73+ The error event is sent out when a fatal (non-recoverable)
74+ error has occurred. The object_id argument is the object
75+ where the error occurred, most often in response to a request
76+ to that object. The code identifies the error and is defined
77+ by the object interface. As such, each interface defines its
78+ own set of error codes. The message is a brief description
79+ of the error, for (debugging) convenience.
80+ </description>
81+ <arg name="object_id" type="object" summary="object where the error occurred"/>
82+ <arg name="code" type="uint" summary="error code"/>
83+ <arg name="message" type="string" summary="error description"/>
84+ </event>
85+
86+ <enum name="error">
87+ <description summary="global error values">
88+ These errors are global and can be emitted in response to any
89+ server request.
90+ </description>
91+ <entry name="invalid_object" value="0"
92+ summary="server couldn't find object"/>
93+ <entry name="invalid_method" value="1"
94+ summary="method doesn't exist on the specified interface or malformed request"/>
95+ <entry name="no_memory" value="2"
96+ summary="server is out of memory"/>
97+ <entry name="implementation" value="3"
98+ summary="implementation error in compositor"/>
99+ </enum>
100+
101+ <event name="delete_id">
102+ <description summary="acknowledge object ID deletion">
103+ This event is used internally by the object ID management logic.
104+
105+ When the server stops using an object created by the client, the server
106+ sends this event. In particular, after sending this event, the server
107+ will no longer send any events that contain the object as the receiver
108+ or as an argument.
109+
110+ When the client receives this event, it knows that it can reuse the
111+ object ID.
112+ </description>
113+ <arg name="id" type="uint" summary="deleted object ID"/>
114+ </event>
115+ </interface>
116+
117+ <interface name="wl_registry" version="1">
118+ <description summary="global registry object">
119+ The singleton global registry object. The server has a number of
120+ global objects that are available to all clients. These objects
121+ typically represent an actual object in the server (for example,
122+ an input device) or they are singleton objects that provide
123+ extension functionality.
124+
125+ When a client creates a registry object, the registry object
126+ will emit a global event for each global currently in the
127+ registry. Globals come and go as a result of device or
128+ monitor hotplugs, reconfiguration or other events, and the
129+ registry will send out global and global_remove events to
130+ keep the client up to date with the changes. To mark the end
131+ of the initial burst of events, the client can use the
132+ wl_display.sync request immediately after calling
133+ wl_display.get_registry.
134+
135+ A client can bind to a global object by using the bind
136+ request. This creates a client-side handle that lets the object
137+ emit events to the client and lets the client invoke requests on
138+ the object.
139+ </description>
140+
141+ <request name="bind">
142+ <description summary="bind an object to the display">
143+ Binds a new, client-created object to the server using the
144+ specified name as the identifier.
145+ </description>
146+ <arg name="name" type="uint" summary="unique numeric name of the object"/>
147+ <arg name="id" type="new_id" summary="bound object"/>
148+ </request>
149+
150+ <event name="global">
151+ <description summary="announce global object">
152+ Notify the client of global objects.
153+
154+ The event notifies the client that a global object with
155+ the given name is now available, and it implements the
156+ given version of the given interface.
157+ </description>
158+ <arg name="name" type="uint" summary="numeric name of the global object"/>
159+ <arg name="interface" type="string" summary="interface implemented by the object"/>
160+ <arg name="version" type="uint" summary="interface version"/>
161+ </event>
162+
163+ <event name="global_remove">
164+ <description summary="announce removal of global object">
165+ Notify the client of removed global objects.
166+
167+ This event notifies the client that the global identified
168+ by name is no longer available. If the client bound to
169+ the global using the bind request, the client should now
170+ destroy that object.
171+
172+ The object remains valid and requests to the object will be
173+ ignored until the client destroys it, to avoid races between
174+ the global going away and a client sending a request to it.
175+ </description>
176+ <arg name="name" type="uint" summary="numeric name of the global object"/>
177+ </event>
178+ </interface>
179+
180+ <interface name="wl_callback" version="1" frozen="true">
181+ <description summary="callback object">
182+ Clients can handle the 'done' event to get notified when
183+ the related request is done.
184+
185+ Note, because wl_callback objects are created from multiple independent
186+ factory interfaces, the wl_callback interface is frozen at version 1.
187+ </description>
188+
189+ <event name="done" type="destructor">
190+ <description summary="done event">
191+ Notify the client when the related request is done.
192+ </description>
193+ <arg name="callback_data" type="uint" summary="request-specific data for the callback"/>
194+ </event>
195+ </interface>
196+
197+ <interface name="wl_compositor" version="7">
198+ <description summary="the compositor singleton">
199+ A compositor. This object is a singleton global. The
200+ compositor is in charge of combining the contents of multiple
201+ surfaces into one displayable output.
202+ </description>
203+
204+ <request name="create_surface">
205+ <description summary="create new surface">
206+ Ask the compositor to create a new surface.
207+ </description>
208+ <arg name="id" type="new_id" interface="wl_surface" summary="the new surface"/>
209+ </request>
210+
211+ <request name="create_region">
212+ <description summary="create new region">
213+ Ask the compositor to create a new region.
214+ </description>
215+ <arg name="id" type="new_id" interface="wl_region" summary="the new region"/>
216+ </request>
217+
218+ <!-- Version 7 additions -->
219+
220+ <request name="release" type="destructor" since="7">
221+ <description summary="destroy wl_compositor">
222+ This request destroys the wl_compositor. This has no effect on any other objects.
223+ </description>
224+ </request>
225+ </interface>
226+
227+ <interface name="wl_shm_pool" version="3">
228+ <description summary="a shared memory pool">
229+ The wl_shm_pool object encapsulates a piece of memory shared
230+ between the compositor and client. Through the wl_shm_pool
231+ object, the client can allocate shared memory wl_buffer objects.
232+ All objects created through the same pool share the same
233+ underlying mapped memory. Reusing the mapped memory avoids the
234+ setup/teardown overhead and is useful when interactively resizing
235+ a surface or for many small buffers.
236+ </description>
237+
238+ <enum name="error" since="3">
239+ <description summary="wl_shm_pool error values">
240+ These errors can be emitted in response to wl_shm_pool requests.
241+ </description>
242+ <entry name="invalid_format" value="0" summary="buffer format is not known"/>
243+ <entry name="invalid_stride" value="1" summary="invalid size or stride during buffer creation"/>
244+ </enum>
245+
246+ <request name="create_buffer">
247+ <description summary="create a buffer from the pool">
248+ Create a wl_buffer object from the pool.
249+
250+ The buffer is created offset bytes into the pool and has
251+ width and height as specified. The stride argument specifies
252+ the number of bytes from the beginning of one row to the beginning
253+ of the next. The format is the pixel format of the buffer and
254+ must be one of those advertised through the wl_shm.format event.
255+
256+ A buffer will keep a reference to the pool it was created from
257+ so it is valid to destroy the pool immediately after creating
258+ a buffer from it.
259+ </description>
260+ <arg name="id" type="new_id" interface="wl_buffer" summary="buffer to create"/>
261+ <arg name="offset" type="int" summary="buffer byte offset within the pool"/>
262+ <arg name="width" type="int" summary="buffer width, in pixels"/>
263+ <arg name="height" type="int" summary="buffer height, in pixels"/>
264+ <arg name="stride" type="int" summary="number of bytes from the beginning of one row to the beginning of the next row"/>
265+ <arg name="format" type="uint" enum="wl_shm.format" summary="buffer pixel format"/>
266+ </request>
267+
268+ <request name="destroy" type="destructor">
269+ <description summary="destroy the pool">
270+ Destroy the shared memory pool.
271+
272+ The mmapped memory will be released when all
273+ buffers that have been created from this pool
274+ are gone.
275+ </description>
276+ </request>
277+
278+ <request name="resize">
279+ <description summary="change the size of the pool mapping">
280+ This request will cause the server to remap the backing memory
281+ for the pool from the file descriptor passed when the pool was
282+ created, but using the new size. This request can only be
283+ used to make the pool bigger.
284+
285+ This request only changes the amount of bytes that are mmapped
286+ by the server and does not touch the file corresponding to the
287+ file descriptor passed at creation time. It is the client's
288+ responsibility to ensure that the file is at least as big as
289+ the new pool size.
290+ </description>
291+ <arg name="size" type="int" summary="new size of the pool, in bytes"/>
292+ </request>
293+ </interface>
294+
295+ <interface name="wl_shm" version="3">
296+ <description summary="shared memory support">
297+ A singleton global object that provides support for shared
298+ memory.
299+
300+ Clients can create wl_shm_pool objects using the create_pool
301+ request.
302+
303+ On binding the wl_shm object one or more format events
304+ are emitted to inform clients about the valid pixel formats
305+ that can be used for buffers.
306+ </description>
307+
308+ <enum name="error" since="3">
309+ <description summary="wl_shm error values">
310+ These errors can be emitted in response to wl_shm requests.
311+ </description>
312+ <entry name="invalid_format" value="0" summary="buffer format is not known" deprecated-since="3"/>
313+ <entry name="invalid_stride" value="1" summary="invalid size or stride during pool creation"/>
314+ <entry name="invalid_fd" value="2" summary="mmapping the file descriptor failed"/>
315+ </enum>
316+
317+ <enum name="format">
318+ <description summary="pixel formats">
319+ This describes the memory layout of an individual pixel.
320+
321+ All renderers should support argb8888 and xrgb8888 but any other
322+ formats are optional and may not be supported by the particular
323+ renderer in use.
324+
325+ The drm format codes match the macros defined in drm_fourcc.h, except
326+ argb8888 and xrgb8888. The formats actually supported by the compositor
327+ will be reported by the format event. See drm_fourcc.h for more detailed
328+ format descriptions.
329+
330+ For all wl_shm formats and unless specified in another protocol
331+ extension, pre-multiplied alpha is used for pixel values.
332+ </description>
333+ <!-- Note to protocol writers: don't update this list manually, instead
334+ run the automated script that keeps it in sync with drm_fourcc.h. -->
335+ <entry name="argb8888" value="0" summary="32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian"/>
336+ <entry name="xrgb8888" value="1" summary="32-bit RGB format, [31:0] x:R:G:B 8:8:8:8 little endian"/>
337+ <entry name="c8" value="0x20203843" summary="8-bit color index format, [7:0] C"/>
338+ <entry name="rgb332" value="0x38424752" summary="8-bit RGB format, [7:0] R:G:B 3:3:2"/>
339+ <entry name="bgr233" value="0x38524742" summary="8-bit BGR format, [7:0] B:G:R 2:3:3"/>
340+ <entry name="xrgb4444" value="0x32315258" summary="16-bit xRGB format, [15:0] x:R:G:B 4:4:4:4 little endian"/>
341+ <entry name="xbgr4444" value="0x32314258" summary="16-bit xBGR format, [15:0] x:B:G:R 4:4:4:4 little endian"/>
342+ <entry name="rgbx4444" value="0x32315852" summary="16-bit RGBx format, [15:0] R:G:B:x 4:4:4:4 little endian"/>
343+ <entry name="bgrx4444" value="0x32315842" summary="16-bit BGRx format, [15:0] B:G:R:x 4:4:4:4 little endian"/>
344+ <entry name="argb4444" value="0x32315241" summary="16-bit ARGB format, [15:0] A:R:G:B 4:4:4:4 little endian"/>
345+ <entry name="abgr4444" value="0x32314241" summary="16-bit ABGR format, [15:0] A:B:G:R 4:4:4:4 little endian"/>
346+ <entry name="rgba4444" value="0x32314152" summary="16-bit RGBA format, [15:0] R:G:B:A 4:4:4:4 little endian"/>
347+ <entry name="bgra4444" value="0x32314142" summary="16-bit BGRA format, [15:0] B:G:R:A 4:4:4:4 little endian"/>
348+ <entry name="xrgb1555" value="0x35315258" summary="16-bit xRGB format, [15:0] x:R:G:B 1:5:5:5 little endian"/>
349+ <entry name="xbgr1555" value="0x35314258" summary="16-bit xBGR 1555 format, [15:0] x:B:G:R 1:5:5:5 little endian"/>
350+ <entry name="rgbx5551" value="0x35315852" summary="16-bit RGBx 5551 format, [15:0] R:G:B:x 5:5:5:1 little endian"/>
351+ <entry name="bgrx5551" value="0x35315842" summary="16-bit BGRx 5551 format, [15:0] B:G:R:x 5:5:5:1 little endian"/>
352+ <entry name="argb1555" value="0x35315241" summary="16-bit ARGB 1555 format, [15:0] A:R:G:B 1:5:5:5 little endian"/>
353+ <entry name="abgr1555" value="0x35314241" summary="16-bit ABGR 1555 format, [15:0] A:B:G:R 1:5:5:5 little endian"/>
354+ <entry name="rgba5551" value="0x35314152" summary="16-bit RGBA 5551 format, [15:0] R:G:B:A 5:5:5:1 little endian"/>
355+ <entry name="bgra5551" value="0x35314142" summary="16-bit BGRA 5551 format, [15:0] B:G:R:A 5:5:5:1 little endian"/>
356+ <entry name="rgb565" value="0x36314752" summary="16-bit RGB 565 format, [15:0] R:G:B 5:6:5 little endian"/>
357+ <entry name="bgr565" value="0x36314742" summary="16-bit BGR 565 format, [15:0] B:G:R 5:6:5 little endian"/>
358+ <entry name="rgb888" value="0x34324752" summary="24-bit RGB format, [23:0] R:G:B little endian"/>
359+ <entry name="bgr888" value="0x34324742" summary="24-bit BGR format, [23:0] B:G:R little endian"/>
360+ <entry name="xbgr8888" value="0x34324258" summary="32-bit xBGR format, [31:0] x:B:G:R 8:8:8:8 little endian"/>
361+ <entry name="rgbx8888" value="0x34325852" summary="32-bit RGBx format, [31:0] R:G:B:x 8:8:8:8 little endian"/>
362+ <entry name="bgrx8888" value="0x34325842" summary="32-bit BGRx format, [31:0] B:G:R:x 8:8:8:8 little endian"/>
363+ <entry name="abgr8888" value="0x34324241" summary="32-bit ABGR format, [31:0] A:B:G:R 8:8:8:8 little endian"/>
364+ <entry name="rgba8888" value="0x34324152" summary="32-bit RGBA format, [31:0] R:G:B:A 8:8:8:8 little endian"/>
365+ <entry name="bgra8888" value="0x34324142" summary="32-bit BGRA format, [31:0] B:G:R:A 8:8:8:8 little endian"/>
366+ <entry name="xrgb2101010" value="0x30335258" summary="32-bit xRGB format, [31:0] x:R:G:B 2:10:10:10 little endian"/>
367+ <entry name="xbgr2101010" value="0x30334258" summary="32-bit xBGR format, [31:0] x:B:G:R 2:10:10:10 little endian"/>
368+ <entry name="rgbx1010102" value="0x30335852" summary="32-bit RGBx format, [31:0] R:G:B:x 10:10:10:2 little endian"/>
369+ <entry name="bgrx1010102" value="0x30335842" summary="32-bit BGRx format, [31:0] B:G:R:x 10:10:10:2 little endian"/>
370+ <entry name="argb2101010" value="0x30335241" summary="32-bit ARGB format, [31:0] A:R:G:B 2:10:10:10 little endian"/>
371+ <entry name="abgr2101010" value="0x30334241" summary="32-bit ABGR format, [31:0] A:B:G:R 2:10:10:10 little endian"/>
372+ <entry name="rgba1010102" value="0x30334152" summary="32-bit RGBA format, [31:0] R:G:B:A 10:10:10:2 little endian"/>
373+ <entry name="bgra1010102" value="0x30334142" summary="32-bit BGRA format, [31:0] B:G:R:A 10:10:10:2 little endian"/>
374+ <entry name="yuyv" value="0x56595559" summary="packed YCbCr format, [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian"/>
375+ <entry name="yvyu" value="0x55595659" summary="packed YCbCr format, [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian"/>
376+ <entry name="uyvy" value="0x59565955" summary="packed YCbCr format, [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian"/>
377+ <entry name="vyuy" value="0x59555956" summary="packed YCbCr format, [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian"/>
378+ <entry name="ayuv" value="0x56555941" summary="packed AYCbCr format, [31:0] A:Y:Cb:Cr 8:8:8:8 little endian"/>
379+ <entry name="nv12" value="0x3231564e" summary="2 plane YCbCr Cr:Cb format, 2x2 subsampled Cr:Cb plane"/>
380+ <entry name="nv21" value="0x3132564e" summary="2 plane YCbCr Cb:Cr format, 2x2 subsampled Cb:Cr plane"/>
381+ <entry name="nv16" value="0x3631564e" summary="2 plane YCbCr Cr:Cb format, 2x1 subsampled Cr:Cb plane"/>
382+ <entry name="nv61" value="0x3136564e" summary="2 plane YCbCr Cb:Cr format, 2x1 subsampled Cb:Cr plane"/>
383+ <entry name="yuv410" value="0x39565559" summary="3 plane YCbCr format, 4x4 subsampled Cb (1) and Cr (2) planes"/>
384+ <entry name="yvu410" value="0x39555659" summary="3 plane YCbCr format, 4x4 subsampled Cr (1) and Cb (2) planes"/>
385+ <entry name="yuv411" value="0x31315559" summary="3 plane YCbCr format, 4x1 subsampled Cb (1) and Cr (2) planes"/>
386+ <entry name="yvu411" value="0x31315659" summary="3 plane YCbCr format, 4x1 subsampled Cr (1) and Cb (2) planes"/>
387+ <entry name="yuv420" value="0x32315559" summary="3 plane YCbCr format, 2x2 subsampled Cb (1) and Cr (2) planes"/>
388+ <entry name="yvu420" value="0x32315659" summary="3 plane YCbCr format, 2x2 subsampled Cr (1) and Cb (2) planes"/>
389+ <entry name="yuv422" value="0x36315559" summary="3 plane YCbCr format, 2x1 subsampled Cb (1) and Cr (2) planes"/>
390+ <entry name="yvu422" value="0x36315659" summary="3 plane YCbCr format, 2x1 subsampled Cr (1) and Cb (2) planes"/>
391+ <entry name="yuv444" value="0x34325559" summary="3 plane YCbCr format, non-subsampled Cb (1) and Cr (2) planes"/>
392+ <entry name="yvu444" value="0x34325659" summary="3 plane YCbCr format, non-subsampled Cr (1) and Cb (2) planes"/>
393+ <entry name="r8" value="0x20203852" summary="[7:0] R"/>
394+ <entry name="r16" value="0x20363152" summary="[15:0] R little endian"/>
395+ <entry name="rg88" value="0x38384752" summary="[15:0] R:G 8:8 little endian"/>
396+ <entry name="gr88" value="0x38385247" summary="[15:0] G:R 8:8 little endian"/>
397+ <entry name="rg1616" value="0x32334752" summary="[31:0] R:G 16:16 little endian"/>
398+ <entry name="gr1616" value="0x32335247" summary="[31:0] G:R 16:16 little endian"/>
399+ <entry name="xrgb16161616f" value="0x48345258" summary="[63:0] x:R:G:B 16:16:16:16 little endian"/>
400+ <entry name="xbgr16161616f" value="0x48344258" summary="[63:0] x:B:G:R 16:16:16:16 little endian"/>
401+ <entry name="argb16161616f" value="0x48345241" summary="[63:0] A:R:G:B 16:16:16:16 little endian"/>
402+ <entry name="abgr16161616f" value="0x48344241" summary="[63:0] A:B:G:R 16:16:16:16 little endian"/>
403+ <entry name="xyuv8888" value="0x56555958" summary="[31:0] X:Y:Cb:Cr 8:8:8:8 little endian"/>
404+ <entry name="vuy888" value="0x34325556" summary="[23:0] Cr:Cb:Y 8:8:8 little endian"/>
405+ <entry name="vuy101010" value="0x30335556" summary="Y followed by U then V, 10:10:10. Non-linear modifier only"/>
406+ <entry name="y210" value="0x30313259" summary="[63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 10:6:10:6:10:6:10:6 little endian per 2 Y pixels"/>
407+ <entry name="y212" value="0x32313259" summary="[63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 12:4:12:4:12:4:12:4 little endian per 2 Y pixels"/>
408+ <entry name="y216" value="0x36313259" summary="[63:0] Cr0:Y1:Cb0:Y0 16:16:16:16 little endian per 2 Y pixels"/>
409+ <entry name="y410" value="0x30313459" summary="[31:0] A:Cr:Y:Cb 2:10:10:10 little endian"/>
410+ <entry name="y412" value="0x32313459" summary="[63:0] A:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian"/>
411+ <entry name="y416" value="0x36313459" summary="[63:0] A:Cr:Y:Cb 16:16:16:16 little endian"/>
412+ <entry name="xvyu2101010" value="0x30335658" summary="[31:0] X:Cr:Y:Cb 2:10:10:10 little endian"/>
413+ <entry name="xvyu12_16161616" value="0x36335658" summary="[63:0] X:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian"/>
414+ <entry name="xvyu16161616" value="0x38345658" summary="[63:0] X:Cr:Y:Cb 16:16:16:16 little endian"/>
415+ <entry name="y0l0" value="0x304c3059" summary="[63:0] A3:A2:Y3:0:Cr0:0:Y2:0:A1:A0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian"/>
416+ <entry name="x0l0" value="0x304c3058" summary="[63:0] X3:X2:Y3:0:Cr0:0:Y2:0:X1:X0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian"/>
417+ <entry name="y0l2" value="0x324c3059" summary="[63:0] A3:A2:Y3:Cr0:Y2:A1:A0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian"/>
418+ <entry name="x0l2" value="0x324c3058" summary="[63:0] X3:X2:Y3:Cr0:Y2:X1:X0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian"/>
419+ <entry name="yuv420_8bit" value="0x38305559"/>
420+ <entry name="yuv420_10bit" value="0x30315559"/>
421+ <entry name="xrgb8888_a8" value="0x38415258"/>
422+ <entry name="xbgr8888_a8" value="0x38414258"/>
423+ <entry name="rgbx8888_a8" value="0x38415852"/>
424+ <entry name="bgrx8888_a8" value="0x38415842"/>
425+ <entry name="rgb888_a8" value="0x38413852"/>
426+ <entry name="bgr888_a8" value="0x38413842"/>
427+ <entry name="rgb565_a8" value="0x38413552"/>
428+ <entry name="bgr565_a8" value="0x38413542"/>
429+ <entry name="nv24" value="0x3432564e" summary="non-subsampled Cr:Cb plane"/>
430+ <entry name="nv42" value="0x3234564e" summary="non-subsampled Cb:Cr plane"/>
431+ <entry name="p210" value="0x30313250" summary="2x1 subsampled Cr:Cb plane, 10 bit per channel"/>
432+ <entry name="p010" value="0x30313050" summary="2x2 subsampled Cr:Cb plane 10 bits per channel"/>
433+ <entry name="p012" value="0x32313050" summary="2x2 subsampled Cr:Cb plane 12 bits per channel"/>
434+ <entry name="p016" value="0x36313050" summary="2x2 subsampled Cr:Cb plane 16 bits per channel"/>
435+ <entry name="axbxgxrx106106106106" value="0x30314241" summary="[63:0] A:x:B:x:G:x:R:x 10:6:10:6:10:6:10:6 little endian"/>
436+ <entry name="nv15" value="0x3531564e" summary="2x2 subsampled Cr:Cb plane"/>
437+ <entry name="q410" value="0x30313451"/>
438+ <entry name="q401" value="0x31303451"/>
439+ <entry name="xrgb16161616" value="0x38345258" summary="[63:0] x:R:G:B 16:16:16:16 little endian"/>
440+ <entry name="xbgr16161616" value="0x38344258" summary="[63:0] x:B:G:R 16:16:16:16 little endian"/>
441+ <entry name="argb16161616" value="0x38345241" summary="[63:0] A:R:G:B 16:16:16:16 little endian"/>
442+ <entry name="abgr16161616" value="0x38344241" summary="[63:0] A:B:G:R 16:16:16:16 little endian"/>
443+ <entry name="c1" value="0x20203143" summary="[7:0] C0:C1:C2:C3:C4:C5:C6:C7 1:1:1:1:1:1:1:1 eight pixels/byte"/>
444+ <entry name="c2" value="0x20203243" summary="[7:0] C0:C1:C2:C3 2:2:2:2 four pixels/byte"/>
445+ <entry name="c4" value="0x20203443" summary="[7:0] C0:C1 4:4 two pixels/byte"/>
446+ <entry name="d1" value="0x20203144" summary="[7:0] D0:D1:D2:D3:D4:D5:D6:D7 1:1:1:1:1:1:1:1 eight pixels/byte"/>
447+ <entry name="d2" value="0x20203244" summary="[7:0] D0:D1:D2:D3 2:2:2:2 four pixels/byte"/>
448+ <entry name="d4" value="0x20203444" summary="[7:0] D0:D1 4:4 two pixels/byte"/>
449+ <entry name="d8" value="0x20203844" summary="[7:0] D"/>
450+ <entry name="r1" value="0x20203152" summary="[7:0] R0:R1:R2:R3:R4:R5:R6:R7 1:1:1:1:1:1:1:1 eight pixels/byte"/>
451+ <entry name="r2" value="0x20203252" summary="[7:0] R0:R1:R2:R3 2:2:2:2 four pixels/byte"/>
452+ <entry name="r4" value="0x20203452" summary="[7:0] R0:R1 4:4 two pixels/byte"/>
453+ <entry name="r10" value="0x20303152" summary="[15:0] x:R 6:10 little endian"/>
454+ <entry name="r12" value="0x20323152" summary="[15:0] x:R 4:12 little endian"/>
455+ <entry name="avuy8888" value="0x59555641" summary="[31:0] A:Cr:Cb:Y 8:8:8:8 little endian"/>
456+ <entry name="xvuy8888" value="0x59555658" summary="[31:0] X:Cr:Cb:Y 8:8:8:8 little endian"/>
457+ <entry name="p030" value="0x30333050" summary="2x2 subsampled Cr:Cb plane 10 bits per channel packed"/>
458+ <entry name="rgb161616" value="0x38344752" summary="[47:0] R:G:B 16:16:16 little endian"/>
459+ <entry name="bgr161616" value="0x38344742" summary="[47:0] B:G:R 16:16:16 little endian"/>
460+ <entry name="r16f" value="0x48202052" summary="[15:0] R 16 little endian"/>
461+ <entry name="gr1616f" value="0x48205247" summary="[31:0] G:R 16:16 little endian"/>
462+ <entry name="bgr161616f" value="0x48524742" summary="[47:0] B:G:R 16:16:16 little endian"/>
463+ <entry name="r32f" value="0x46202052" summary="[31:0] R 32 little endian"/>
464+ <entry name="gr3232f" value="0x46205247" summary="[63:0] G:R 32:32 little endian"/>
465+ <entry name="bgr323232f" value="0x46524742" summary="[95:0] B:G:R 32:32:32 little endian"/>
466+ <entry name="abgr32323232f" value="0x46384241" summary="[127:0] A:B:G:R 32:32:32:32 little endian"/>
467+ <entry name="nv20" value="0x3032564e" summary="2x1 subsampled Cr:Cb plane"/>
468+ <entry name="nv30" value="0x3033564e" summary="non-subsampled Cr:Cb plane"/>
469+ <entry name="s010" value="0x30313053" summary="2x2 subsampled Cb (1) and Cr (2) planes 10 bits per channel"/>
470+ <entry name="s210" value="0x30313253" summary="2x1 subsampled Cb (1) and Cr (2) planes 10 bits per channel"/>
471+ <entry name="s410" value="0x30313453" summary="non-subsampled Cb (1) and Cr (2) planes 10 bits per channel"/>
472+ <entry name="s012" value="0x32313053" summary="2x2 subsampled Cb (1) and Cr (2) planes 12 bits per channel"/>
473+ <entry name="s212" value="0x32313253" summary="2x1 subsampled Cb (1) and Cr (2) planes 12 bits per channel"/>
474+ <entry name="s412" value="0x32313453" summary="non-subsampled Cb (1) and Cr (2) planes 12 bits per channel"/>
475+ <entry name="s016" value="0x36313053" summary="2x2 subsampled Cb (1) and Cr (2) planes 16 bits per channel"/>
476+ <entry name="s216" value="0x36313253" summary="2x1 subsampled Cb (1) and Cr (2) planes 16 bits per channel"/>
477+ <entry name="s416" value="0x36313453" summary="non-subsampled Cb (1) and Cr (2) planes 16 bits per channel"/>
478+ <entry name="xvuy2101010" value="0x30335958" summary="[31:0] x:Cr:Cb:Y 2:10:10:10 little endian"/>
479+ <entry name="p230" value="0x30333250" summary="2x1 subsampled Cr:Cb plane 10 bits per channel packed"/>
480+ <entry name="t430" value="0x30333454"/>
481+ <entry name="y8" value="0x59455247" summary="8-bit Y-only"/>
482+ <entry name="xyyy2101010" value="0x34415059" summary="[31:0] x:Y2:Y1:Y0 2:10:10:10 little endian"/>
483+ </enum>
484+
485+ <request name="create_pool">
486+ <description summary="create a shm pool">
487+ Create a new wl_shm_pool object.
488+
489+ The pool can be used to create shared memory based buffer
490+ objects. The server will mmap size bytes of the passed file
491+ descriptor, to use as backing memory for the pool.
492+ </description>
493+ <arg name="id" type="new_id" interface="wl_shm_pool" summary="pool to create"/>
494+ <arg name="fd" type="fd" summary="file descriptor for the pool"/>
495+ <arg name="size" type="int" summary="pool size, in bytes"/>
496+ </request>
497+
498+ <event name="format">
499+ <description summary="pixel format description">
500+ Informs the client about a valid pixel format that
501+ can be used for buffers. Known formats include
502+ argb8888 and xrgb8888.
503+
504+ Extensions to drm_fourcc.h (or the format enum) do not require
505+ increasing the wl_shm version; as a result, clients may receive format
506+ codes which were not in the list at the time the client was made.
507+ </description>
508+ <arg name="format" type="uint" enum="format" summary="buffer pixel format"/>
509+ </event>
510+
511+ <!-- Version 2 additions -->
512+
513+ <request name="release" type="destructor" since="2">
514+ <description summary="release the shm object">
515+ Using this request a client can tell the server that it is not going to
516+ use the shm object anymore.
517+
518+ Objects created via this interface remain unaffected.
519+ </description>
520+ </request>
521+ </interface>
522+
523+ <interface name="wl_buffer" version="1" frozen="true">
524+ <description summary="content for a wl_surface">
525+ A buffer provides the content for a wl_surface. Buffers are
526+ created through factory interfaces such as wl_shm, wp_linux_buffer_params
527+ (from the linux-dmabuf protocol extension) or similar. It has a width and
528+ a height and can be attached to a wl_surface, but the mechanism by which a
529+ client provides and updates the contents is defined by the buffer factory
530+ interface.
531+
532+ Color channels are assumed to be electrical rather than optical (in other
533+ words, encoded with a transfer function) unless otherwise specified. If
534+ the buffer uses a format that has an alpha channel, the alpha channel is
535+ assumed to be premultiplied into the electrical color channel values
536+ (after transfer function encoding) unless otherwise specified.
537+
538+ Note, because wl_buffer objects are created from multiple independent
539+ factory interfaces, the wl_buffer interface is frozen at version 1.
540+ </description>
541+
542+ <request name="destroy" type="destructor">
543+ <description summary="destroy a buffer">
544+ Destroy a buffer. If and how you need to release the backing
545+ storage is defined by the buffer factory interface.
546+
547+ For possible side-effects to a surface, see wl_surface.attach.
548+ </description>
549+ </request>
550+
551+ <event name="release">
552+ <description summary="compositor releases buffer">
553+ Sent when this wl_buffer is no longer used by the compositor.
554+
555+ For more information on when release events may or may not be sent,
556+ and what consequences it has, please see the description of
557+ wl_surface.attach.
558+
559+ If a client receives a release event before the frame callback
560+ requested in the same wl_surface.commit that attaches this
561+ wl_buffer to a surface, then the client is immediately free to
562+ reuse the buffer and its backing storage, and does not need a
563+ second buffer for the next surface content update. Typically
564+ this is possible, when the compositor maintains a copy of the
565+ wl_surface contents, e.g. as a GL texture. This is an important
566+ optimization for GL(ES) compositors with wl_shm clients.
567+ </description>
568+ </event>
569+ </interface>
570+
571+ <interface name="wl_data_offer" version="4">
572+ <description summary="offer to transfer data">
573+ A wl_data_offer represents a piece of data offered for transfer
574+ by another client (the source client). It is used by the
575+ copy-and-paste and drag-and-drop mechanisms. The offer
576+ describes the different mime types that the data can be
577+ converted to and provides the mechanism for transferring the
578+ data directly from the source client.
579+ </description>
580+
581+ <enum name="error">
582+ <entry name="invalid_finish" value="0"
583+ summary="finish request was called untimely"/>
584+ <entry name="invalid_action_mask" value="1"
585+ summary="action mask contains invalid values"/>
586+ <entry name="invalid_action" value="2"
587+ summary="action argument has an invalid value"/>
588+ <entry name="invalid_offer" value="3"
589+ summary="offer doesn't accept this request"/>
590+ </enum>
591+
592+ <request name="accept">
593+ <description summary="accept one of the offered mime types">
594+ Indicate that the client can accept the given mime type, or
595+ NULL for not accepted.
596+
597+ For objects of version 2 or older, this request is used by the
598+ client to give feedback whether the client can receive the given
599+ mime type, or NULL if none is accepted; the feedback does not
600+ determine whether the drag-and-drop operation succeeds or not.
601+
602+ For objects of version 3 or newer, this request determines the
603+ final result of the drag-and-drop operation. If the end result
604+ is that no mime types were accepted, the drag-and-drop operation
605+ will be cancelled and the corresponding drag source will receive
606+ wl_data_source.cancelled. Clients may still use this event in
607+ conjunction with wl_data_source.action for feedback.
608+ </description>
609+ <arg name="serial" type="uint" summary="serial number of the accept request"/>
610+ <arg name="mime_type" type="string" allow-null="true" summary="mime type accepted by the client"/>
611+ </request>
612+
613+ <request name="receive">
614+ <description summary="request that the data is transferred">
615+ To transfer the offered data, the client issues this request
616+ and indicates the mime type it wants to receive. The transfer
617+ happens through the passed file descriptor (typically created
618+ with the pipe system call). The source client writes the data
619+ in the mime type representation requested and then closes the
620+ file descriptor.
621+
622+ The receiving client reads from the read end of the pipe until
623+ EOF and then closes its end, at which point the transfer is
624+ complete.
625+
626+ This request may happen multiple times for different mime types,
627+ both before and after wl_data_device.drop. Drag-and-drop destination
628+ clients may preemptively fetch data or examine it more closely to
629+ determine acceptance.
630+ </description>
631+ <arg name="mime_type" type="string" summary="mime type desired by receiver"/>
632+ <arg name="fd" type="fd" summary="file descriptor for data transfer"/>
633+ </request>
634+
635+ <request name="destroy" type="destructor">
636+ <description summary="destroy data offer">
637+ Destroy the data offer.
638+ </description>
639+ </request>
640+
641+ <event name="offer">
642+ <description summary="advertise offered mime type">
643+ Sent immediately after creating the wl_data_offer object. One
644+ event per offered mime type.
645+ </description>
646+ <arg name="mime_type" type="string" summary="offered mime type"/>
647+ </event>
648+
649+ <!-- Version 3 additions -->
650+
651+ <request name="finish" since="3">
652+ <description summary="the offer will no longer be used">
653+ Notifies the compositor that the drag destination successfully
654+ finished the drag-and-drop operation.
655+
656+ Upon receiving this request, the compositor will emit
657+ wl_data_source.dnd_finished on the drag source client.
658+
659+ It is a client error to perform other requests than
660+ wl_data_offer.destroy after this one. It is also an error to perform
661+ this request after a NULL mime type has been set in
662+ wl_data_offer.accept or no action was received through
663+ wl_data_offer.action.
664+
665+ If wl_data_offer.finish request is received for a non drag and drop
666+ operation, the invalid_finish protocol error is raised.
667+ </description>
668+ </request>
669+
670+ <request name="set_actions" since="3">
671+ <description summary="set the available/preferred drag-and-drop actions">
672+ Sets the actions that the destination side client supports for
673+ this operation. This request may trigger the emission of
674+ wl_data_source.action and wl_data_offer.action events if the compositor
675+ needs to change the selected action.
676+
677+ This request can be called multiple times throughout the
678+ drag-and-drop operation, typically in response to wl_data_device.enter
679+ or wl_data_device.motion events.
680+
681+ This request determines the final result of the drag-and-drop
682+ operation. If the end result is that no action is accepted,
683+ the drag source will receive wl_data_source.cancelled.
684+
685+ The dnd_actions argument must contain only values expressed in the
686+ wl_data_device_manager.dnd_actions enum, and the preferred_action
687+ argument must only contain one of those values set, otherwise it
688+ will result in a protocol error.
689+
690+ While managing an "ask" action, the destination drag-and-drop client
691+ may perform further wl_data_offer.receive requests, and is expected
692+ to perform one last wl_data_offer.set_actions request with a preferred
693+ action other than "ask" (and optionally wl_data_offer.accept) before
694+ requesting wl_data_offer.finish, in order to convey the action selected
695+ by the user. If the preferred action is not in the
696+ wl_data_offer.source_actions mask, an error will be raised.
697+
698+ If the "ask" action is dismissed (e.g. user cancellation), the client
699+ is expected to perform wl_data_offer.destroy right away.
700+
701+ This request can only be made on drag-and-drop offers, a protocol error
702+ will be raised otherwise.
703+ </description>
704+ <arg name="dnd_actions" type="uint" summary="actions supported by the destination client"
705+ enum="wl_data_device_manager.dnd_action"/>
706+ <arg name="preferred_action" type="uint" summary="action preferred by the destination client"
707+ enum="wl_data_device_manager.dnd_action"/>
708+ </request>
709+
710+ <event name="source_actions" since="3">
711+ <description summary="notify the source-side available actions">
712+ This event indicates the actions offered by the data source. It
713+ will be sent immediately after creating the wl_data_offer object,
714+ or anytime the source side changes its offered actions through
715+ wl_data_source.set_actions.
716+ </description>
717+ <arg name="source_actions" type="uint" summary="actions offered by the data source"
718+ enum="wl_data_device_manager.dnd_action"/>
719+ </event>
720+
721+ <event name="action" since="3">
722+ <description summary="notify the selected action">
723+ This event indicates the action selected by the compositor after
724+ matching the source/destination side actions. Only one action (or
725+ none) will be offered here.
726+
727+ This event can be emitted multiple times during the drag-and-drop
728+ operation in response to destination side action changes through
729+ wl_data_offer.set_actions.
730+
731+ This event will no longer be emitted after wl_data_device.drop
732+ happened on the drag-and-drop destination, the client must
733+ honor the last action received, or the last preferred one set
734+ through wl_data_offer.set_actions when handling an "ask" action.
735+
736+ Compositors may also change the selected action on the fly, mainly
737+ in response to keyboard modifier changes during the drag-and-drop
738+ operation.
739+
740+ The most recent action received is always the valid one. Prior to
741+ receiving wl_data_device.drop, the chosen action may change (e.g.
742+ due to keyboard modifiers being pressed). At the time of receiving
743+ wl_data_device.drop the drag-and-drop destination must honor the
744+ last action received.
745+
746+ Action changes may still happen after wl_data_device.drop,
747+ especially on "ask" actions, where the drag-and-drop destination
748+ may choose another action afterwards. Action changes happening
749+ at this stage are always the result of inter-client negotiation, the
750+ compositor shall no longer be able to induce a different action.
751+
752+ Upon "ask" actions, it is expected that the drag-and-drop destination
753+ may potentially choose a different action and/or mime type,
754+ based on wl_data_offer.source_actions and finally chosen by the
755+ user (e.g. popping up a menu with the available options). The
756+ final wl_data_offer.set_actions and wl_data_offer.accept requests
757+ must happen before the call to wl_data_offer.finish.
758+ </description>
759+ <arg name="dnd_action" type="uint" summary="action selected by the compositor"
760+ enum="wl_data_device_manager.dnd_action"/>
761+ </event>
762+ </interface>
763+
764+ <interface name="wl_data_source" version="4">
765+ <description summary="offer to transfer data">
766+ The wl_data_source object is the source side of a wl_data_offer.
767+ It is created by the source client in a data transfer and
768+ provides a way to describe the offered data and a way to respond
769+ to requests to transfer the data.
770+ </description>
771+
772+ <enum name="error">
773+ <entry name="invalid_action_mask" value="0"
774+ summary="action mask contains invalid values"/>
775+ <entry name="invalid_source" value="1"
776+ summary="source doesn't accept this request"/>
777+ </enum>
778+
779+ <request name="offer">
780+ <description summary="add an offered mime type">
781+ This request adds a mime type to the set of mime types
782+ advertised to targets. Can be called several times to offer
783+ multiple types.
784+ </description>
785+ <arg name="mime_type" type="string" summary="mime type offered by the data source"/>
786+ </request>
787+
788+ <request name="destroy" type="destructor">
789+ <description summary="destroy the data source">
790+ Destroy the data source.
791+ </description>
792+ </request>
793+
794+ <event name="target">
795+ <description summary="a target accepts an offered mime type">
796+ Sent when a target accepts pointer_focus or motion events. If
797+ a target does not accept any of the offered types, type is NULL.
798+
799+ Used for feedback during drag-and-drop.
800+ </description>
801+ <arg name="mime_type" type="string" allow-null="true" summary="mime type accepted by the target"/>
802+ </event>
803+
804+ <event name="send">
805+ <description summary="send the data">
806+ Request for data from the client. Send the data as the
807+ specified mime type over the passed file descriptor, then
808+ close it.
809+ </description>
810+ <arg name="mime_type" type="string" summary="mime type for the data"/>
811+ <arg name="fd" type="fd" summary="file descriptor for the data"/>
812+ </event>
813+
814+ <event name="cancelled">
815+ <description summary="selection was cancelled">
816+ This data source is no longer valid. There are several reasons why
817+ this could happen:
818+
819+ - The data source has been replaced by another data source.
820+ - The drag-and-drop operation was performed, but the drop destination
821+ did not accept any of the mime types offered through
822+ wl_data_source.target.
823+ - The drag-and-drop operation was performed, but the drop destination
824+ did not select any of the actions present in the mask offered through
825+ wl_data_source.action.
826+ - The drag-and-drop operation was performed but didn't happen over a
827+ surface.
828+ - The compositor cancelled the drag-and-drop operation (e.g. compositor
829+ dependent timeouts to avoid stale drag-and-drop transfers).
830+
831+ The client should clean up and destroy this data source.
832+
833+ For objects of version 2 or older, wl_data_source.cancelled will
834+ only be emitted if the data source was replaced by another data
835+ source.
836+ </description>
837+ </event>
838+
839+ <!-- Version 3 additions -->
840+
841+ <request name="set_actions" since="3">
842+ <description summary="set the available drag-and-drop actions">
843+ Sets the actions that the source side client supports for this
844+ operation. This request may trigger wl_data_source.action and
845+ wl_data_offer.action events if the compositor needs to change the
846+ selected action.
847+
848+ The dnd_actions argument must contain only values expressed in the
849+ wl_data_device_manager.dnd_actions enum, otherwise it will result
850+ in a protocol error.
851+
852+ This request must be made once only, and can only be made on sources
853+ used in drag-and-drop, so it must be performed before
854+ wl_data_device.start_drag. Attempting to use the source other than
855+ for drag-and-drop will raise a protocol error.
856+ </description>
857+ <arg name="dnd_actions" type="uint" summary="actions supported by the data source"
858+ enum="wl_data_device_manager.dnd_action"/>
859+ </request>
860+
861+ <event name="dnd_drop_performed" since="3">
862+ <description summary="the drag-and-drop operation physically finished">
863+ The user performed the drop action. This event does not indicate
864+ acceptance, wl_data_source.cancelled may still be emitted afterwards
865+ if the drop destination does not accept any mime type.
866+
867+ However, this event might not be received if the compositor cancelled
868+ the drag-and-drop operation before this event could happen.
869+
870+ Note that the data_source may still be used in the future and should
871+ not be destroyed here.
872+ </description>
873+ </event>
874+
875+ <event name="dnd_finished" since="3">
876+ <description summary="the drag-and-drop operation concluded">
877+ The drop destination finished interoperating with this data
878+ source, so the client is now free to destroy this data source and
879+ free all associated data.
880+
881+ If the action used to perform the operation was "move", the
882+ source can now delete the transferred data.
883+ </description>
884+ </event>
885+
886+ <event name="action" since="3">
887+ <description summary="notify the selected action">
888+ This event indicates the action selected by the compositor after
889+ matching the source/destination side actions. Only one action (or
890+ none) will be offered here.
891+
892+ This event can be emitted multiple times during the drag-and-drop
893+ operation, mainly in response to destination side changes through
894+ wl_data_offer.set_actions, and as the data device enters/leaves
895+ surfaces.
896+
897+ It is only possible to receive this event after
898+ wl_data_source.dnd_drop_performed if the drag-and-drop operation
899+ ended in an "ask" action, in which case the final wl_data_source.action
900+ event will happen immediately before wl_data_source.dnd_finished.
901+
902+ Compositors may also change the selected action on the fly, mainly
903+ in response to keyboard modifier changes during the drag-and-drop
904+ operation.
905+
906+ The most recent action received is always the valid one. The chosen
907+ action may change alongside negotiation (e.g. an "ask" action can turn
908+ into a "move" operation), so the effects of the final action must
909+ always be applied in wl_data_source.dnd_finished.
910+
911+ Clients can trigger cursor surface changes from this point, so
912+ they reflect the current action.
913+ </description>
914+ <arg name="dnd_action" type="uint" summary="action selected by the compositor"
915+ enum="wl_data_device_manager.dnd_action"/>
916+ </event>
917+ </interface>
918+
919+ <interface name="wl_data_device" version="4">
920+ <description summary="data transfer device">
921+ There is one wl_data_device per seat which can be obtained
922+ from the global wl_data_device_manager singleton.
923+
924+ A wl_data_device provides access to inter-client data transfer
925+ mechanisms such as copy-and-paste and drag-and-drop.
926+ </description>
927+
928+ <enum name="error">
929+ <entry name="role" value="0" summary="given wl_surface has another role"/>
930+ <entry name="used_source" value="1" summary="source has already been used"/>
931+ </enum>
932+
933+ <request name="start_drag">
934+ <description summary="start drag-and-drop operation">
935+ This request asks the compositor to start a drag-and-drop
936+ operation on behalf of the client.
937+
938+ The source argument is the data source that provides the data
939+ for the eventual data transfer. If source is NULL, enter, leave
940+ and motion events are sent only to the client that initiated the
941+ drag and the client is expected to handle the data passing
942+ internally. If source is destroyed, the drag-and-drop session will be
943+ cancelled.
944+
945+ The origin surface is the surface where the drag originates and
946+ the client must have an active implicit grab that matches the
947+ serial.
948+
949+ The icon surface is an optional (can be NULL) surface that
950+ provides an icon to be moved around with the cursor. Initially,
951+ the top-left corner of the icon surface is placed at the cursor
952+ hotspot, but subsequent wl_surface.offset requests can move the
953+ relative position. Attach requests must be confirmed with
954+ wl_surface.commit as usual. The icon surface is given the role of
955+ a drag-and-drop icon. If the icon surface already has another role,
956+ it raises a protocol error.
957+
958+ The input region is ignored for wl_surfaces with the role of a
959+ drag-and-drop icon.
960+
961+ The given source may not be used in any further set_selection or
962+ start_drag requests. Attempting to reuse a previously-used source
963+ may send a used_source error.
964+ </description>
965+ <arg name="source" type="object" interface="wl_data_source" allow-null="true" summary="data source for the eventual transfer"/>
966+ <arg name="origin" type="object" interface="wl_surface" summary="surface where the drag originates"/>
967+ <arg name="icon" type="object" interface="wl_surface" allow-null="true" summary="drag-and-drop icon surface"/>
968+ <arg name="serial" type="uint" summary="serial number of the implicit grab on the origin"/>
969+ </request>
970+
971+ <request name="set_selection">
972+ <description summary="copy data to the selection">
973+ This request asks the compositor to set the selection
974+ to the data from the source on behalf of the client.
975+
976+ To unset the selection, set the source to NULL.
977+
978+ The given source may not be used in any further set_selection or
979+ start_drag requests. Attempting to reuse a previously-used source
980+ may send a used_source error.
981+ </description>
982+ <arg name="source" type="object" interface="wl_data_source" allow-null="true" summary="data source for the selection"/>
983+ <arg name="serial" type="uint" summary="serial number of the event that triggered this request"/>
984+ </request>
985+
986+ <event name="data_offer">
987+ <description summary="introduce a new wl_data_offer">
988+ The data_offer event introduces a new wl_data_offer object,
989+ which will subsequently be used in either the
990+ data_device.enter event (for drag-and-drop) or the
991+ data_device.selection event (for selections). Immediately
992+ following the data_device.data_offer event, the new data_offer
993+ object will send out data_offer.offer events to describe the
994+ mime types it offers.
995+ </description>
996+ <arg name="id" type="new_id" interface="wl_data_offer" summary="the new data_offer object"/>
997+ </event>
998+
999+ <event name="enter">
1000+ <description summary="initiate drag-and-drop session">
1001+ This event is sent when an active drag-and-drop pointer enters
1002+ a surface owned by the client. The position of the pointer at
1003+ enter time is provided by the x and y arguments, in surface-local
1004+ coordinates.
1005+ </description>
1006+ <arg name="serial" type="uint" summary="serial number of the enter event"/>
1007+ <arg name="surface" type="object" interface="wl_surface" summary="client surface entered"/>
1008+ <arg name="x" type="fixed" summary="surface-local x coordinate"/>
1009+ <arg name="y" type="fixed" summary="surface-local y coordinate"/>
1010+ <arg name="id" type="object" interface="wl_data_offer" allow-null="true"
1011+ summary="source data_offer object"/>
1012+ </event>
1013+
1014+ <event name="leave">
1015+ <description summary="end drag-and-drop session">
1016+ This event is sent when the drag-and-drop pointer leaves the
1017+ surface and the session ends. The client must destroy the
1018+ wl_data_offer introduced at enter time at this point.
1019+ </description>
1020+ </event>
1021+
1022+ <event name="motion">
1023+ <description summary="drag-and-drop session motion">
1024+ This event is sent when the drag-and-drop pointer moves within
1025+ the currently focused surface. The new position of the pointer
1026+ is provided by the x and y arguments, in surface-local
1027+ coordinates.
1028+ </description>
1029+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
1030+ <arg name="x" type="fixed" summary="surface-local x coordinate"/>
1031+ <arg name="y" type="fixed" summary="surface-local y coordinate"/>
1032+ </event>
1033+
1034+ <event name="drop">
1035+ <description summary="end drag-and-drop session successfully">
1036+ The event is sent when a drag-and-drop operation is ended
1037+ because the implicit grab is removed.
1038+
1039+ The drag-and-drop destination is expected to honor the last action
1040+ received through wl_data_offer.action, if the resulting action is
1041+ "copy" or "move", the destination can still perform
1042+ wl_data_offer.receive requests, and is expected to end all
1043+ transfers with a wl_data_offer.finish request.
1044+
1045+ If the resulting action is "ask", the action will not be considered
1046+ final. The drag-and-drop destination is expected to perform one last
1047+ wl_data_offer.set_actions request, or wl_data_offer.destroy in order
1048+ to cancel the operation.
1049+ </description>
1050+ </event>
1051+
1052+ <event name="selection">
1053+ <description summary="advertise new selection">
1054+ The selection event is sent out to notify the client of a new
1055+ wl_data_offer for the selection for this device. The
1056+ data_device.data_offer and the data_offer.offer events are
1057+ sent out immediately before this event to introduce the data
1058+ offer object. The selection event is sent to a client
1059+ immediately before receiving keyboard focus and when a new
1060+ selection is set while the client has keyboard focus. The
1061+ data_offer is valid until a new data_offer or NULL is received
1062+ or until the client loses keyboard focus. Switching surface with
1063+ keyboard focus within the same client doesn't mean a new selection
1064+ will be sent. The client must destroy the previous selection
1065+ data_offer, if any, upon receiving this event.
1066+ </description>
1067+ <arg name="id" type="object" interface="wl_data_offer" allow-null="true"
1068+ summary="selection data_offer object"/>
1069+ </event>
1070+
1071+ <!-- Version 2 additions -->
1072+
1073+ <request name="release" type="destructor" since="2">
1074+ <description summary="destroy data device">
1075+ This request destroys the data device.
1076+ </description>
1077+ </request>
1078+ </interface>
1079+
1080+ <interface name="wl_data_device_manager" version="4">
1081+ <description summary="data transfer interface">
1082+ The wl_data_device_manager is a singleton global object that
1083+ provides access to inter-client data transfer mechanisms such as
1084+ copy-and-paste and drag-and-drop. These mechanisms are tied to
1085+ a wl_seat and this interface lets a client get a wl_data_device
1086+ corresponding to a wl_seat.
1087+
1088+ Depending on the version bound, the objects created from the bound
1089+ wl_data_device_manager object will have different requirements for
1090+ functioning properly. See wl_data_source.set_actions,
1091+ wl_data_offer.accept and wl_data_offer.finish for details.
1092+ </description>
1093+
1094+ <request name="create_data_source">
1095+ <description summary="create a new data source">
1096+ Create a new data source.
1097+ </description>
1098+ <arg name="id" type="new_id" interface="wl_data_source" summary="data source to create"/>
1099+ </request>
1100+
1101+ <request name="get_data_device">
1102+ <description summary="create a new data device">
1103+ Create a new data device for a given seat.
1104+ </description>
1105+ <arg name="id" type="new_id" interface="wl_data_device" summary="data device to create"/>
1106+ <arg name="seat" type="object" interface="wl_seat" summary="seat associated with the data device"/>
1107+ </request>
1108+
1109+ <!-- Version 3 additions -->
1110+
1111+ <enum name="dnd_action" bitfield="true" since="3">
1112+ <description summary="drag and drop actions">
1113+ This is a bitmask of the available/preferred actions in a
1114+ drag-and-drop operation.
1115+
1116+ In the compositor, the selected action is a result of matching the
1117+ actions offered by the source and destination sides. "action" events
1118+ with a "none" action will be sent to both source and destination if
1119+ there is no match. All further checks will effectively happen on
1120+ (source actions ∩ destination actions).
1121+
1122+ In addition, compositors may also pick different actions in
1123+ reaction to key modifiers being pressed. One common design that
1124+ is used in major toolkits (and the behavior recommended for
1125+ compositors) is:
1126+
1127+ - If no modifiers are pressed, the first match (in bit order)
1128+ will be used.
1129+ - Pressing Shift selects "move", if enabled in the mask.
1130+ - Pressing Control selects "copy", if enabled in the mask.
1131+
1132+ Behavior beyond that is considered implementation-dependent.
1133+ Compositors may for example bind other modifiers (like Alt/Meta)
1134+ or drags initiated with other buttons than BTN_LEFT to specific
1135+ actions (e.g. "ask").
1136+ </description>
1137+ <entry name="none" value="0" summary="no action"/>
1138+ <entry name="copy" value="1" summary="copy action"/>
1139+ <entry name="move" value="2" summary="move action"/>
1140+ <entry name="ask" value="4" summary="ask action"/>
1141+ </enum>
1142+
1143+ <!-- Version 4 additions -->
1144+
1145+ <request name="release" type="destructor" since="4">
1146+ <description summary="destroy wl_data_device_manager">
1147+ This request destroys the wl_data_device_manager. This has no effect on any other
1148+ objects.
1149+ </description>
1150+ </request>
1151+ </interface>
1152+
1153+ <interface name="wl_shell" version="1">
1154+ <description summary="create desktop-style surfaces">
1155+ This interface is implemented by servers that provide
1156+ desktop-style user interfaces.
1157+
1158+ It allows clients to associate a wl_shell_surface with
1159+ a basic surface.
1160+
1161+ Note! This protocol is deprecated and not intended for production use.
1162+ For desktop-style user interfaces, use xdg_shell. Compositors and clients
1163+ should not implement this interface.
1164+ </description>
1165+
1166+ <enum name="error">
1167+ <entry name="role" value="0" summary="given wl_surface has another role"/>
1168+ </enum>
1169+
1170+ <request name="get_shell_surface">
1171+ <description summary="create a shell surface from a surface">
1172+ Create a shell surface for an existing surface. This gives
1173+ the wl_surface the role of a shell surface. If the wl_surface
1174+ already has another role, it raises a protocol error.
1175+
1176+ Only one shell surface can be associated with a given surface.
1177+ </description>
1178+ <arg name="id" type="new_id" interface="wl_shell_surface" summary="shell surface to create"/>
1179+ <arg name="surface" type="object" interface="wl_surface" summary="surface to be given the shell surface role"/>
1180+ </request>
1181+ </interface>
1182+
1183+ <interface name="wl_shell_surface" version="1">
1184+ <description summary="desktop-style metadata interface">
1185+ An interface that may be implemented by a wl_surface, for
1186+ implementations that provide a desktop-style user interface.
1187+
1188+ It provides requests to treat surfaces like toplevel, fullscreen
1189+ or popup windows, move, resize or maximize them, associate
1190+ metadata like title and class, etc.
1191+
1192+ On the server side the object is automatically destroyed when
1193+ the related wl_surface is destroyed. On the client side,
1194+ wl_shell_surface_destroy() must be called before destroying
1195+ the wl_surface object.
1196+ </description>
1197+
1198+ <request name="pong">
1199+ <description summary="respond to a ping event">
1200+ A client must respond to a ping event with a pong request or
1201+ the client may be deemed unresponsive.
1202+ </description>
1203+ <arg name="serial" type="uint" summary="serial number of the ping event"/>
1204+ </request>
1205+
1206+ <request name="move">
1207+ <description summary="start an interactive move">
1208+ Start a pointer-driven move of the surface.
1209+
1210+ This request must be used in response to a button press event.
1211+ The server may ignore move requests depending on the state of
1212+ the surface (e.g. fullscreen or maximized).
1213+ </description>
1214+ <arg name="seat" type="object" interface="wl_seat" summary="seat whose pointer is used"/>
1215+ <arg name="serial" type="uint" summary="serial number of the implicit grab on the pointer"/>
1216+ </request>
1217+
1218+ <enum name="resize" bitfield="true">
1219+ <description summary="edge values for resizing">
1220+ These values are used to indicate which edge of a surface
1221+ is being dragged in a resize operation. The server may
1222+ use this information to adapt its behavior, e.g. choose
1223+ an appropriate cursor image.
1224+ </description>
1225+ <entry name="none" value="0" summary="no edge"/>
1226+ <entry name="top" value="1" summary="top edge"/>
1227+ <entry name="bottom" value="2" summary="bottom edge"/>
1228+ <entry name="left" value="4" summary="left edge"/>
1229+ <entry name="top_left" value="5" summary="top and left edges"/>
1230+ <entry name="bottom_left" value="6" summary="bottom and left edges"/>
1231+ <entry name="right" value="8" summary="right edge"/>
1232+ <entry name="top_right" value="9" summary="top and right edges"/>
1233+ <entry name="bottom_right" value="10" summary="bottom and right edges"/>
1234+ </enum>
1235+
1236+ <request name="resize">
1237+ <description summary="start an interactive resize">
1238+ Start a pointer-driven resizing of the surface.
1239+
1240+ This request must be used in response to a button press event.
1241+ The server may ignore resize requests depending on the state of
1242+ the surface (e.g. fullscreen or maximized).
1243+ </description>
1244+ <arg name="seat" type="object" interface="wl_seat" summary="seat whose pointer is used"/>
1245+ <arg name="serial" type="uint" summary="serial number of the implicit grab on the pointer"/>
1246+ <arg name="edges" type="uint" enum="resize" summary="which edge or corner is being dragged"/>
1247+ </request>
1248+
1249+ <request name="set_toplevel">
1250+ <description summary="make the surface a toplevel surface">
1251+ Map the surface as a toplevel surface.
1252+
1253+ A toplevel surface is not fullscreen, maximized or transient.
1254+ </description>
1255+ </request>
1256+
1257+ <enum name="transient" bitfield="true">
1258+ <description summary="details of transient behaviour">
1259+ These flags specify details of the expected behaviour
1260+ of transient surfaces. Used in the set_transient request.
1261+ </description>
1262+ <entry name="inactive" value="0x1" summary="do not set keyboard focus"/>
1263+ </enum>
1264+
1265+ <request name="set_transient">
1266+ <description summary="make the surface a transient surface">
1267+ Map the surface relative to an existing surface.
1268+
1269+ The x and y arguments specify the location of the upper left
1270+ corner of the surface relative to the upper left corner of the
1271+ parent surface, in surface-local coordinates.
1272+
1273+ The flags argument controls details of the transient behaviour.
1274+ </description>
1275+ <arg name="parent" type="object" interface="wl_surface" summary="parent surface"/>
1276+ <arg name="x" type="int" summary="surface-local x coordinate"/>
1277+ <arg name="y" type="int" summary="surface-local y coordinate"/>
1278+ <arg name="flags" type="uint" enum="transient" summary="transient surface behavior"/>
1279+ </request>
1280+
1281+ <enum name="fullscreen_method">
1282+ <description summary="different method to set the surface fullscreen">
1283+ Hints to indicate to the compositor how to deal with a conflict
1284+ between the dimensions of the surface and the dimensions of the
1285+ output. The compositor is free to ignore this parameter.
1286+ </description>
1287+ <entry name="default" value="0" summary="no preference, apply default policy"/>
1288+ <entry name="scale" value="1" summary="scale, preserve the surface's aspect ratio and center on output"/>
1289+ <entry name="driver" value="2" summary="switch output mode to the smallest mode that can fit the surface, add black borders to compensate size mismatch"/>
1290+ <entry name="fill" value="3" summary="no upscaling, center on output and add black borders to compensate size mismatch"/>
1291+ </enum>
1292+
1293+ <request name="set_fullscreen">
1294+ <description summary="make the surface a fullscreen surface">
1295+ Map the surface as a fullscreen surface.
1296+
1297+ If an output parameter is given then the surface will be made
1298+ fullscreen on that output. If the client does not specify the
1299+ output then the compositor will apply its policy - usually
1300+ choosing the output on which the surface has the biggest surface
1301+ area.
1302+
1303+ The client may specify a method to resolve a size conflict
1304+ between the output size and the surface size - this is provided
1305+ through the method parameter.
1306+
1307+ The framerate parameter is used only when the method is set
1308+ to "driver", to indicate the preferred framerate. A value of 0
1309+ indicates that the client does not care about framerate. The
1310+ framerate is specified in mHz, that is framerate of 60000 is 60Hz.
1311+
1312+ A method of "scale" or "driver" implies a scaling operation of
1313+ the surface, either via a direct scaling operation or a change of
1314+ the output mode. This will override any kind of output scaling, so
1315+ that mapping a surface with a buffer size equal to the mode can
1316+ fill the screen independent of buffer_scale.
1317+
1318+ A method of "fill" means we don't scale up the buffer, however
1319+ any output scale is applied. This means that you may run into
1320+ an edge case where the application maps a buffer with the same
1321+ size of the output mode but buffer_scale 1 (thus making a
1322+ surface larger than the output). In this case it is allowed to
1323+ downscale the results to fit the screen.
1324+
1325+ The compositor must reply to this request with a configure event
1326+ with the dimensions for the output on which the surface will
1327+ be made fullscreen.
1328+ </description>
1329+ <arg name="method" type="uint" enum="fullscreen_method" summary="method for resolving size conflict"/>
1330+ <arg name="framerate" type="uint" summary="framerate in mHz"/>
1331+ <arg name="output" type="object" interface="wl_output" allow-null="true"
1332+ summary="output on which the surface is to be fullscreen"/>
1333+ </request>
1334+
1335+ <request name="set_popup">
1336+ <description summary="make the surface a popup surface">
1337+ Map the surface as a popup.
1338+
1339+ A popup surface is a transient surface with an added pointer
1340+ grab.
1341+
1342+ An existing implicit grab will be changed to owner-events mode,
1343+ and the popup grab will continue after the implicit grab ends
1344+ (i.e. releasing the mouse button does not cause the popup to
1345+ be unmapped).
1346+
1347+ The popup grab continues until the window is destroyed or a
1348+ mouse button is pressed in any other client's window. A click
1349+ in any of the client's surfaces is reported as normal, however,
1350+ clicks in other clients' surfaces will be discarded and trigger
1351+ the callback.
1352+
1353+ The x and y arguments specify the location of the upper left
1354+ corner of the surface relative to the upper left corner of the
1355+ parent surface, in surface-local coordinates.
1356+ </description>
1357+ <arg name="seat" type="object" interface="wl_seat" summary="seat whose pointer is used"/>
1358+ <arg name="serial" type="uint" summary="serial number of the implicit grab on the pointer"/>
1359+ <arg name="parent" type="object" interface="wl_surface" summary="parent surface"/>
1360+ <arg name="x" type="int" summary="surface-local x coordinate"/>
1361+ <arg name="y" type="int" summary="surface-local y coordinate"/>
1362+ <arg name="flags" type="uint" enum="transient" summary="transient surface behavior"/>
1363+ </request>
1364+
1365+ <request name="set_maximized">
1366+ <description summary="make the surface a maximized surface">
1367+ Map the surface as a maximized surface.
1368+
1369+ If an output parameter is given then the surface will be
1370+ maximized on that output. If the client does not specify the
1371+ output then the compositor will apply its policy - usually
1372+ choosing the output on which the surface has the biggest surface
1373+ area.
1374+
1375+ The compositor will reply with a configure event telling
1376+ the expected new surface size. The operation is completed
1377+ on the next buffer attach to this surface.
1378+
1379+ A maximized surface typically fills the entire output it is
1380+ bound to, except for desktop elements such as panels. This is
1381+ the main difference between a maximized shell surface and a
1382+ fullscreen shell surface.
1383+
1384+ The details depend on the compositor implementation.
1385+ </description>
1386+ <arg name="output" type="object" interface="wl_output" allow-null="true"
1387+ summary="output on which the surface is to be maximized"/>
1388+ </request>
1389+
1390+ <request name="set_title">
1391+ <description summary="set surface title">
1392+ Set a short title for the surface.
1393+
1394+ This string may be used to identify the surface in a task bar,
1395+ window list, or other user interface elements provided by the
1396+ compositor.
1397+
1398+ The string must be encoded in UTF-8.
1399+ </description>
1400+ <arg name="title" type="string" summary="surface title"/>
1401+ </request>
1402+
1403+ <request name="set_class">
1404+ <description summary="set surface class">
1405+ Set a class for the surface.
1406+
1407+ The surface class identifies the general class of applications
1408+ to which the surface belongs. A common convention is to use the
1409+ file name (or the full path if it is a non-standard location) of
1410+ the application's .desktop file as the class.
1411+ </description>
1412+ <arg name="class_" type="string" summary="surface class"/>
1413+ </request>
1414+
1415+ <event name="ping">
1416+ <description summary="ping client">
1417+ Ping a client to check if it is receiving events and sending
1418+ requests. A client is expected to reply with a pong request.
1419+ </description>
1420+ <arg name="serial" type="uint" summary="serial number of the ping"/>
1421+ </event>
1422+
1423+ <event name="configure">
1424+ <description summary="suggest resize">
1425+ The configure event asks the client to resize its surface.
1426+
1427+ The size is a hint, in the sense that the client is free to
1428+ ignore it if it doesn't resize, pick a smaller size (to
1429+ satisfy aspect ratio or resize in steps of NxM pixels).
1430+
1431+ The edges parameter provides a hint about how the surface
1432+ was resized. The client may use this information to decide
1433+ how to adjust its content to the new size (e.g. a scrolling
1434+ area might adjust its content position to leave the viewable
1435+ content unmoved).
1436+
1437+ The client is free to dismiss all but the last configure
1438+ event it received.
1439+
1440+ The width and height arguments specify the size of the window
1441+ in surface-local coordinates.
1442+ </description>
1443+ <arg name="edges" type="uint" enum="resize" summary="how the surface was resized"/>
1444+ <arg name="width" type="int" summary="new width of the surface"/>
1445+ <arg name="height" type="int" summary="new height of the surface"/>
1446+ </event>
1447+
1448+ <event name="popup_done">
1449+ <description summary="popup interaction is done">
1450+ The popup_done event is sent out when a popup grab is broken,
1451+ that is, when the user clicks a surface that doesn't belong
1452+ to the client owning the popup surface.
1453+ </description>
1454+ </event>
1455+ </interface>
1456+
1457+ <interface name="wl_surface" version="7">
1458+ <description summary="an onscreen surface">
1459+ A surface is a rectangular area that may be displayed on zero
1460+ or more outputs, and shown any number of times at the compositor's
1461+ discretion. They can present wl_buffers, receive user input, and
1462+ define a local coordinate system.
1463+
1464+ The size of a surface (and relative positions on it) is described
1465+ in surface-local coordinates, which may differ from the buffer
1466+ coordinates of the pixel content, in case a buffer_transform
1467+ or a buffer_scale is used.
1468+
1469+ A surface without a "role" is fairly useless: a compositor does
1470+ not know where, when or how to present it. The role is the
1471+ purpose of a wl_surface. Examples of roles are a cursor for a
1472+ pointer (as set by wl_pointer.set_cursor), a drag icon
1473+ (wl_data_device.start_drag), a sub-surface
1474+ (wl_subcompositor.get_subsurface), and a window as defined by a
1475+ shell protocol (e.g. wl_shell.get_shell_surface).
1476+
1477+ A surface can have only one role at a time. Initially a
1478+ wl_surface does not have a role. Once a wl_surface is given a
1479+ role, it is set permanently for the whole lifetime of the
1480+ wl_surface object. Giving the current role again is allowed,
1481+ unless explicitly forbidden by the relevant interface
1482+ specification.
1483+
1484+ Surface roles are given by requests in other interfaces such as
1485+ wl_pointer.set_cursor. The request should explicitly mention
1486+ that this request gives a role to a wl_surface. Often, this
1487+ request also creates a new protocol object that represents the
1488+ role and adds additional functionality to wl_surface. When a
1489+ client wants to destroy a wl_surface, they must destroy this role
1490+ object before the wl_surface, otherwise a defunct_role_object error is
1491+ sent.
1492+
1493+ Destroying the role object does not remove the role from the
1494+ wl_surface, but it may stop the wl_surface from "playing the role".
1495+ For instance, if a wl_subsurface object is destroyed, the wl_surface
1496+ it was created for will be unmapped and forget its position and
1497+ z-order. It is allowed to create a wl_subsurface for the same
1498+ wl_surface again, but it is not allowed to use the wl_surface as
1499+ a cursor (cursor is a different role than sub-surface, and role
1500+ switching is not allowed).
1501+ </description>
1502+
1503+ <enum name="error">
1504+ <description summary="wl_surface error values">
1505+ These errors can be emitted in response to wl_surface requests.
1506+ </description>
1507+ <entry name="invalid_scale" value="0" summary="buffer scale value is invalid"/>
1508+ <entry name="invalid_transform" value="1" summary="buffer transform value is invalid"/>
1509+ <entry name="invalid_size" value="2" summary="buffer size is invalid"/>
1510+ <entry name="invalid_offset" value="3" summary="buffer offset is invalid"/>
1511+ <entry name="defunct_role_object" value="4"
1512+ summary="surface was destroyed before its role object"/>
1513+ <entry name="no_buffer" value="5" summary="no buffer was attached"/>
1514+ </enum>
1515+
1516+ <request name="destroy" type="destructor">
1517+ <description summary="delete surface">
1518+ Deletes the surface and invalidates its object ID.
1519+ </description>
1520+ </request>
1521+
1522+ <request name="attach">
1523+ <description summary="set the surface contents">
1524+ Set a buffer as the content of this surface.
1525+
1526+ The new size of the surface is calculated based on the buffer
1527+ size transformed by the inverse buffer_transform and the
1528+ inverse buffer_scale. This means that at commit time the supplied
1529+ buffer size must be an integer multiple of the buffer_scale. If
1530+ that's not the case, an invalid_size error is sent.
1531+
1532+ The x and y arguments specify the location of the new pending
1533+ buffer's upper left corner, relative to the current buffer's upper
1534+ left corner, in surface-local coordinates. In other words, the
1535+ x and y, combined with the new surface size define in which
1536+ directions the surface's size changes. Setting anything other than 0
1537+ as x and y arguments is discouraged, and should instead be replaced
1538+ with using the separate wl_surface.offset request.
1539+
1540+ When the bound wl_surface version is 5 or higher, passing any
1541+ non-zero x or y is a protocol violation, and will result in an
1542+ 'invalid_offset' error being raised. The x and y arguments are ignored
1543+ and do not change the pending state. To achieve equivalent semantics,
1544+ use wl_surface.offset.
1545+
1546+ Surface contents are double-buffered state, see wl_surface.commit.
1547+
1548+ The initial surface contents are void; there is no content.
1549+ wl_surface.attach assigns the given wl_buffer as the pending
1550+ wl_buffer. wl_surface.commit makes the pending wl_buffer the new
1551+ surface contents, and the size of the surface becomes the size
1552+ calculated from the wl_buffer, as described above. After commit,
1553+ there is no pending buffer until the next attach.
1554+
1555+ Committing a pending wl_buffer allows the compositor to read the
1556+ pixels in the wl_buffer. The compositor may access the pixels at
1557+ any time after the wl_surface.commit request. When the compositor
1558+ will not access the pixels anymore, it will send the
1559+ wl_buffer.release event. Only after receiving wl_buffer.release,
1560+ the client may reuse the wl_buffer. A wl_buffer that has been
1561+ attached and then replaced by another attach instead of committed
1562+ will not receive a release event, and is not used by the
1563+ compositor.
1564+
1565+ If a pending wl_buffer has been committed to more than one wl_surface,
1566+ the delivery of wl_buffer.release events becomes undefined. A well
1567+ behaved client should not rely on wl_buffer.release events in this
1568+ case. Instead, clients hitting this case should use
1569+ wl_surface.get_release or use a protocol extension providing per-commit
1570+ release notifications (if none of these options are available, a
1571+ fallback can be implemented by creating multiple wl_buffer objects from
1572+ the same backing storage).
1573+
1574+ Destroying the wl_buffer after wl_buffer.release does not change
1575+ the surface contents. Destroying the wl_buffer before wl_buffer.release
1576+ is allowed as long as the underlying buffer storage isn't re-used (this
1577+ can happen e.g. on client process termination). However, if the client
1578+ destroys the wl_buffer before receiving the wl_buffer.release event and
1579+ mutates the underlying buffer storage, the surface contents become
1580+ undefined immediately.
1581+
1582+ If wl_surface.attach is sent with a NULL wl_buffer, the
1583+ following wl_surface.commit will remove the surface content.
1584+
1585+ If a pending wl_buffer has been destroyed, the result is not specified.
1586+ Many compositors are known to remove the surface content on the following
1587+ wl_surface.commit, but this behaviour is not universal. Clients seeking to
1588+ maximise compatibility should not destroy pending buffers and should
1589+ ensure that they explicitly remove content from surfaces, even after
1590+ destroying buffers.
1591+ </description>
1592+ <arg name="buffer" type="object" interface="wl_buffer" allow-null="true"
1593+ summary="buffer of surface contents"/>
1594+ <arg name="x" type="int" summary="surface-local x coordinate"/>
1595+ <arg name="y" type="int" summary="surface-local y coordinate"/>
1596+ </request>
1597+
1598+ <request name="damage">
1599+ <description summary="mark part of the surface damaged">
1600+ This request is used to describe the regions where the pending
1601+ buffer is different from the current surface contents, and where
1602+ the surface therefore needs to be repainted. The compositor
1603+ ignores the parts of the damage that fall outside of the surface.
1604+
1605+ Damage is double-buffered state, see wl_surface.commit.
1606+
1607+ The damage rectangle is specified in surface-local coordinates,
1608+ where x and y specify the upper left corner of the damage rectangle.
1609+
1610+ The initial value for pending damage is empty: no damage.
1611+ wl_surface.damage adds pending damage: the new pending damage
1612+ is the union of old pending damage and the given rectangle.
1613+
1614+ wl_surface.commit assigns pending damage as the current damage,
1615+ and clears pending damage. The server will clear the current
1616+ damage as it repaints the surface.
1617+
1618+ Note! New clients should not use this request. Instead damage can be
1619+ posted with wl_surface.damage_buffer which uses buffer coordinates
1620+ instead of surface coordinates.
1621+ </description>
1622+ <arg name="x" type="int" summary="surface-local x coordinate"/>
1623+ <arg name="y" type="int" summary="surface-local y coordinate"/>
1624+ <arg name="width" type="int" summary="width of damage rectangle"/>
1625+ <arg name="height" type="int" summary="height of damage rectangle"/>
1626+ </request>
1627+
1628+ <request name="frame">
1629+ <description summary="request a frame throttling hint">
1630+ Request a notification when it is a good time to start drawing a new
1631+ frame, by creating a frame callback. This is useful for throttling
1632+ redrawing operations, and driving animations.
1633+
1634+ When a client is animating on a wl_surface, it can use the 'frame'
1635+ request to get notified when it is a good time to draw and commit the
1636+ next frame of animation. If the client commits an update earlier than
1637+ that, it is likely that some updates will not make it to the display,
1638+ and the client is wasting resources by drawing too often.
1639+
1640+ The frame request will take effect on the next wl_surface.commit.
1641+ The notification will only be posted for one frame unless
1642+ requested again. For a wl_surface, the notifications are posted in
1643+ the order the frame requests were committed.
1644+
1645+ The server must send the notifications so that a client
1646+ will not send excessive updates, while still allowing
1647+ the highest possible update rate for clients that wait for the reply
1648+ before drawing again. The server should give some time for the client
1649+ to draw and commit after sending the frame callback events to let it
1650+ hit the next output refresh.
1651+
1652+ A server should avoid signaling the frame callbacks if the
1653+ surface is not visible in any way, e.g. the surface is off-screen,
1654+ or completely obscured by other opaque surfaces.
1655+
1656+ The object returned by this request will be destroyed by the
1657+ compositor after the callback is fired and as such the client must not
1658+ attempt to use it after that point.
1659+
1660+ The callback_data passed in the callback is the current time, in
1661+ milliseconds, with an undefined base.
1662+ </description>
1663+ <arg name="callback" type="new_id" interface="wl_callback" summary="callback object for the frame request"/>
1664+ </request>
1665+
1666+ <request name="set_opaque_region">
1667+ <description summary="set opaque region">
1668+ This request sets the region of the surface that contains
1669+ opaque content.
1670+
1671+ The opaque region is an optimization hint for the compositor
1672+ that lets it optimize the redrawing of content behind opaque
1673+ regions. Setting an opaque region is not required for correct
1674+ behaviour, but marking transparent content as opaque will result
1675+ in repaint artifacts.
1676+
1677+ The opaque region is specified in surface-local coordinates.
1678+
1679+ The compositor ignores the parts of the opaque region that fall
1680+ outside of the surface.
1681+
1682+ Opaque region is double-buffered state, see wl_surface.commit.
1683+
1684+ wl_surface.set_opaque_region changes the pending opaque region.
1685+ wl_surface.commit copies the pending region to the current region.
1686+ Otherwise, the pending and current regions are never changed.
1687+
1688+ The initial value for an opaque region is empty. Setting the pending
1689+ opaque region has copy semantics, and the wl_region object can be
1690+ destroyed immediately. A NULL wl_region causes the pending opaque
1691+ region to be set to empty.
1692+ </description>
1693+ <arg name="region" type="object" interface="wl_region" allow-null="true"
1694+ summary="opaque region of the surface"/>
1695+ </request>
1696+
1697+ <request name="set_input_region">
1698+ <description summary="set input region">
1699+ This request sets the region of the surface that can receive
1700+ pointer and touch events.
1701+
1702+ Input events happening outside of this region will try the next
1703+ surface in the server surface stack. The compositor ignores the
1704+ parts of the input region that fall outside of the surface.
1705+
1706+ The input region is specified in surface-local coordinates.
1707+
1708+ Input region is double-buffered state, see wl_surface.commit.
1709+
1710+ wl_surface.set_input_region changes the pending input region.
1711+ wl_surface.commit copies the pending region to the current region.
1712+ Otherwise the pending and current regions are never changed,
1713+ except cursor and icon surfaces are special cases, see
1714+ wl_pointer.set_cursor and wl_data_device.start_drag.
1715+
1716+ The initial value for an input region is infinite. That means the
1717+ whole surface will accept input. Setting the pending input region
1718+ has copy semantics, and the wl_region object can be destroyed
1719+ immediately. A NULL wl_region causes the input region to be set
1720+ to infinite.
1721+ </description>
1722+ <arg name="region" type="object" interface="wl_region" allow-null="true"
1723+ summary="input region of the surface"/>
1724+ </request>
1725+
1726+ <request name="commit">
1727+ <description summary="commit pending surface state">
1728+ Surface state (input, opaque, and damage regions, attached buffers,
1729+ etc.) is double-buffered. Protocol requests modify the pending state,
1730+ as opposed to the active state in use by the compositor.
1731+
1732+ All requests that need a commit to become effective are documented
1733+ to affect double-buffered state.
1734+
1735+ Other interfaces may add further double-buffered surface state.
1736+
1737+ A commit request atomically creates a Content Update (CU) from the
1738+ pending state, even if the pending state has not been touched. The
1739+ content update is placed at the end of a per-surface queue until it
1740+ becomes active. After commit, the new pending state is as documented for
1741+ each related request.
1742+
1743+ A CU is either a Desync Content Update (DCU) or a Sync Content Update
1744+ (SCU). If the surface is effectively synchronized at the commit request,
1745+ it is a SCU, otherwise a DCU.
1746+
1747+ When a surface transitions from effectively synchronized to effectively
1748+ desynchronized, all SCUs in its queue which are not reachable by any
1749+ DCU become DCUs and dependency edges from outside the queue to these CUs
1750+ are removed.
1751+
1752+ See wl_subsurface for the definition of 'effectively synchronized' and
1753+ 'effectively desynchronized'.
1754+
1755+ When a CU is placed in the queue, the CU has a dependency on the CU in
1756+ front of it and to the SCU at end of the queue of every direct child
1757+ surface if that SCU exists and does not have another dependent. This can
1758+ form a directed acyclic graph of CUs with dependencies as edges.
1759+
1760+ In addition to surface state, the CU can have constraints that must be
1761+ satisfied before it can be applied. Other interfaces may add CU
1762+ constraints.
1763+
1764+ All DCUs which do not have a SCU in front of themselves in their queue,
1765+ are candidates. If the graph that's reachable by a candidate does not
1766+ have any unsatisfied constraints, the entire graph must be applied
1767+ atomically.
1768+
1769+ When a CU is applied, the wl_buffer is applied before all other state.
1770+ This means that all coordinates in double-buffered state are relative to
1771+ the newly attached wl_buffers, except for wl_surface.attach itself. If
1772+ there is no newly attached wl_buffer, the coordinates are relative to
1773+ the previous content update.
1774+ </description>
1775+ </request>
1776+
1777+ <event name="enter">
1778+ <description summary="surface enters an output">
1779+ This is emitted whenever a surface's creation, movement, or resizing
1780+ results in some part of it being within the scanout region of an
1781+ output.
1782+
1783+ Note that a surface may be overlapping with zero or more outputs.
1784+ </description>
1785+ <arg name="output" type="object" interface="wl_output" summary="output entered by the surface"/>
1786+ </event>
1787+
1788+ <event name="leave">
1789+ <description summary="surface leaves an output">
1790+ This is emitted whenever a surface's creation, movement, or resizing
1791+ results in it no longer having any part of it within the scanout region
1792+ of an output.
1793+
1794+ Clients should not use the number of outputs the surface is on for frame
1795+ throttling purposes. The surface might be hidden even if no leave event
1796+ has been sent, and the compositor might expect new surface content
1797+ updates even if no enter event has been sent. The frame event should be
1798+ used instead.
1799+ </description>
1800+ <arg name="output" type="object" interface="wl_output" summary="output left by the surface"/>
1801+ </event>
1802+
1803+ <!-- Version 2 additions -->
1804+
1805+ <request name="set_buffer_transform" since="2">
1806+ <description summary="sets the buffer transformation">
1807+ This request sets the transformation that the client has already applied
1808+ to the content of the buffer. The accepted values for the transform
1809+ parameter are the values for wl_output.transform.
1810+
1811+ The compositor applies the inverse of this transformation whenever it
1812+ uses the buffer contents.
1813+
1814+ Buffer transform is double-buffered state, see wl_surface.commit.
1815+
1816+ A newly created surface has its buffer transformation set to normal.
1817+
1818+ wl_surface.set_buffer_transform changes the pending buffer
1819+ transformation. wl_surface.commit copies the pending buffer
1820+ transformation to the current one. Otherwise, the pending and current
1821+ values are never changed.
1822+
1823+ The purpose of this request is to allow clients to render content
1824+ according to the output transform, thus permitting the compositor to
1825+ use certain optimizations even if the display is rotated. Using
1826+ hardware overlays and scanning out a client buffer for fullscreen
1827+ surfaces are examples of such optimizations. Those optimizations are
1828+ highly dependent on the compositor implementation, so the use of this
1829+ request should be considered on a case-by-case basis.
1830+
1831+ Note that if the transform value includes 90 or 270 degree rotation,
1832+ the width of the buffer will become the surface height and the height
1833+ of the buffer will become the surface width.
1834+
1835+ If transform is not one of the values from the
1836+ wl_output.transform enum the invalid_transform protocol error
1837+ is raised.
1838+ </description>
1839+ <arg name="transform" type="int" enum="wl_output.transform"
1840+ summary="transform for interpreting buffer contents"/>
1841+ </request>
1842+
1843+ <!-- Version 3 additions -->
1844+
1845+ <request name="set_buffer_scale" since="3">
1846+ <description summary="sets the buffer scaling factor">
1847+ This request sets an optional scaling factor on how the compositor
1848+ interprets the contents of the buffer attached to the window.
1849+
1850+ Buffer scale is double-buffered state, see wl_surface.commit.
1851+
1852+ A newly created surface has its buffer scale set to 1.
1853+
1854+ wl_surface.set_buffer_scale changes the pending buffer scale.
1855+ wl_surface.commit copies the pending buffer scale to the current one.
1856+ Otherwise, the pending and current values are never changed.
1857+
1858+ The purpose of this request is to allow clients to supply higher
1859+ resolution buffer data for use on high resolution outputs. It is
1860+ intended that you pick the same buffer scale as the scale of the
1861+ output that the surface is displayed on. This means the compositor
1862+ can avoid scaling when rendering the surface on that output.
1863+
1864+ Note that if the scale is larger than 1, then you have to attach
1865+ a buffer that is larger (by a factor of scale in each dimension)
1866+ than the desired surface size.
1867+
1868+ If scale is not greater than 0 the invalid_scale protocol error is
1869+ raised.
1870+ </description>
1871+ <arg name="scale" type="int"
1872+ summary="scale for interpreting buffer contents"/>
1873+ </request>
1874+
1875+ <!-- Version 4 additions -->
1876+ <request name="damage_buffer" since="4">
1877+ <description summary="mark part of the surface damaged using buffer coordinates">
1878+ This request is used to describe the regions where the pending
1879+ buffer is different from the current surface contents, and where
1880+ the surface therefore needs to be repainted. The compositor
1881+ ignores the parts of the damage that fall outside of the surface.
1882+
1883+ Damage is double-buffered state, see wl_surface.commit.
1884+
1885+ The damage rectangle is specified in buffer coordinates,
1886+ where x and y specify the upper left corner of the damage rectangle.
1887+
1888+ The initial value for pending damage is empty: no damage.
1889+ wl_surface.damage_buffer adds pending damage: the new pending
1890+ damage is the union of old pending damage and the given rectangle.
1891+
1892+ wl_surface.commit assigns pending damage as the current damage,
1893+ and clears pending damage. The server will clear the current
1894+ damage as it repaints the surface.
1895+
1896+ This request differs from wl_surface.damage in only one way - it
1897+ takes damage in buffer coordinates instead of surface-local
1898+ coordinates. While this generally is more intuitive than surface
1899+ coordinates, it is especially desirable when using wp_viewport
1900+ or when a drawing library (like EGL) is unaware of buffer scale
1901+ and buffer transform.
1902+
1903+ Note: Because buffer transformation changes and damage requests may
1904+ be interleaved in the protocol stream, it is impossible to determine
1905+ the actual mapping between surface and buffer damage until
1906+ wl_surface.commit time. Therefore, compositors wishing to take both
1907+ kinds of damage into account will have to accumulate damage from the
1908+ two requests separately and only transform from one to the other
1909+ after receiving the wl_surface.commit.
1910+ </description>
1911+ <arg name="x" type="int" summary="buffer-local x coordinate"/>
1912+ <arg name="y" type="int" summary="buffer-local y coordinate"/>
1913+ <arg name="width" type="int" summary="width of damage rectangle"/>
1914+ <arg name="height" type="int" summary="height of damage rectangle"/>
1915+ </request>
1916+
1917+ <!-- Version 5 additions -->
1918+
1919+ <request name="offset" since="5">
1920+ <description summary="set the surface contents offset">
1921+ The x and y arguments specify the location of the new pending
1922+ buffer's upper left corner, relative to the current buffer's upper
1923+ left corner, in surface-local coordinates. In other words, the
1924+ x and y, combined with the new surface size define in which
1925+ directions the surface's size changes.
1926+
1927+ The exact semantics of wl_surface.offset are role-specific. Refer to
1928+ the documentation of specific roles for more information.
1929+
1930+ Surface location offset is double-buffered state, see
1931+ wl_surface.commit.
1932+
1933+ This request is semantically equivalent to and the replaces the x and y
1934+ arguments in the wl_surface.attach request in wl_surface versions prior
1935+ to 5. See wl_surface.attach for details.
1936+ </description>
1937+ <arg name="x" type="int" summary="surface-local x coordinate"/>
1938+ <arg name="y" type="int" summary="surface-local y coordinate"/>
1939+ </request>
1940+
1941+ <!-- Version 6 additions -->
1942+
1943+ <event name="preferred_buffer_scale" since="6">
1944+ <description summary="preferred buffer scale for the surface">
1945+ This event indicates the preferred buffer scale for this surface. It is
1946+ sent whenever the compositor's preference changes.
1947+
1948+ Before receiving this event the preferred buffer scale for this surface
1949+ is 1.
1950+
1951+ It is intended that scaling aware clients use this event to scale their
1952+ content and use wl_surface.set_buffer_scale to indicate the scale they
1953+ have rendered with. This allows clients to supply a higher detail
1954+ buffer.
1955+
1956+ The compositor shall emit a scale value greater than 0.
1957+ </description>
1958+ <arg name="factor" type="int" summary="preferred scaling factor"/>
1959+ </event>
1960+
1961+ <event name="preferred_buffer_transform" since="6">
1962+ <description summary="preferred buffer transform for the surface">
1963+ This event indicates the preferred buffer transform for this surface.
1964+ It is sent whenever the compositor's preference changes.
1965+
1966+ Before receiving this event the preferred buffer transform for this
1967+ surface is normal.
1968+
1969+ Applying this transformation to the surface buffer contents and using
1970+ wl_surface.set_buffer_transform might allow the compositor to use the
1971+ surface buffer more efficiently.
1972+ </description>
1973+ <arg name="transform" type="uint" enum="wl_output.transform"
1974+ summary="preferred transform"/>
1975+ </event>
1976+
1977+ <!-- Version 7 additions -->
1978+
1979+ <request name="get_release" since="7">
1980+ <description summary="get a release callback">
1981+ Create a callback for the release of the buffer attached by the client
1982+ with wl_surface.attach.
1983+
1984+ The compositor will release the buffer when it has finished its usage of
1985+ the underlying storage for the relevant commit. Once the client receives
1986+ this event, and assuming the associated buffer is not pending release
1987+ from other wl_surface.commit requests, the client can safely re-use the
1988+ buffer.
1989+
1990+ Release callbacks are double-buffered state, and will be associated
1991+ with the pending buffer at wl_surface.commit time.
1992+
1993+ The callback_data passed in the wl_callback.done event is unused and
1994+ is always zero.
1995+
1996+ Sending this request without attaching a non-null buffer in the same
1997+ content update is a protocol error. The compositor will send the
1998+ no_buffer error in this case.
1999+ </description>
2000+ <arg name="callback" type="new_id" interface="wl_callback" summary="callback object for the release"/>
2001+ </request>
2002+ </interface>
2003+
2004+ <interface name="wl_seat" version="11">
2005+ <description summary="group of input devices">
2006+ A seat is a group of keyboards, pointer and touch devices. This
2007+ object is published as a global during start up, or when such a
2008+ device is hot plugged. A seat typically has a pointer and
2009+ maintains a keyboard focus and a pointer focus.
2010+ </description>
2011+
2012+ <enum name="capability" bitfield="true">
2013+ <description summary="seat capability bitmask">
2014+ This is a bitmask of capabilities this seat has; if a member is
2015+ set, then it is present on the seat.
2016+ </description>
2017+ <entry name="pointer" value="1" summary="the seat has pointer devices"/>
2018+ <entry name="keyboard" value="2" summary="the seat has one or more keyboards"/>
2019+ <entry name="touch" value="4" summary="the seat has touch devices"/>
2020+ </enum>
2021+
2022+ <enum name="error">
2023+ <description summary="wl_seat error values">
2024+ These errors can be emitted in response to wl_seat requests.
2025+ </description>
2026+ <entry name="missing_capability" value="0"
2027+ summary="get_pointer, get_keyboard or get_touch called on seat without the matching capability"/>
2028+ </enum>
2029+
2030+ <event name="capabilities">
2031+ <description summary="seat capabilities changed">
2032+ This is sent on binding to the seat global or whenever a seat gains
2033+ or loses the pointer, keyboard or touch capabilities.
2034+ The argument is a capability enum containing the complete set of
2035+ capabilities this seat has.
2036+
2037+ When the pointer capability is added, a client may create a
2038+ wl_pointer object using the wl_seat.get_pointer request. This object
2039+ will receive pointer events until the capability is removed in the
2040+ future.
2041+
2042+ When the pointer capability is removed, a client should destroy the
2043+ wl_pointer objects associated with the seat where the capability was
2044+ removed, using the wl_pointer.release request. No further pointer
2045+ events will be received on these objects.
2046+
2047+ In some compositors, if a seat regains the pointer capability and a
2048+ client has a previously obtained wl_pointer object of version 4 or
2049+ less, that object may start sending pointer events again. This
2050+ behavior is considered a misinterpretation of the intended behavior
2051+ and must not be relied upon by the client. wl_pointer objects of
2052+ version 5 or later must not send events if created before the most
2053+ recent event notifying the client of an added pointer capability.
2054+
2055+ The above behavior also applies to wl_keyboard and wl_touch with the
2056+ keyboard and touch capabilities, respectively.
2057+ </description>
2058+ <arg name="capabilities" type="uint" enum="capability" summary="capabilities of the seat"/>
2059+ </event>
2060+
2061+ <request name="get_pointer">
2062+ <description summary="return pointer object">
2063+ The ID provided will be initialized to the wl_pointer interface
2064+ for this seat.
2065+
2066+ This request only takes effect if the seat has the pointer
2067+ capability, or has had the pointer capability in the past.
2068+ It is a protocol violation to issue this request on a seat that has
2069+ never had the pointer capability. The missing_capability error will
2070+ be sent in this case.
2071+ </description>
2072+ <arg name="id" type="new_id" interface="wl_pointer" summary="seat pointer"/>
2073+ </request>
2074+
2075+ <request name="get_keyboard">
2076+ <description summary="return keyboard object">
2077+ The ID provided will be initialized to the wl_keyboard interface
2078+ for this seat.
2079+
2080+ This request only takes effect if the seat has the keyboard
2081+ capability, or has had the keyboard capability in the past.
2082+ It is a protocol violation to issue this request on a seat that has
2083+ never had the keyboard capability. The missing_capability error will
2084+ be sent in this case.
2085+ </description>
2086+ <arg name="id" type="new_id" interface="wl_keyboard" summary="seat keyboard"/>
2087+ </request>
2088+
2089+ <request name="get_touch">
2090+ <description summary="return touch object">
2091+ The ID provided will be initialized to the wl_touch interface
2092+ for this seat.
2093+
2094+ This request only takes effect if the seat has the touch
2095+ capability, or has had the touch capability in the past.
2096+ It is a protocol violation to issue this request on a seat that has
2097+ never had the touch capability. The missing_capability error will
2098+ be sent in this case.
2099+ </description>
2100+ <arg name="id" type="new_id" interface="wl_touch" summary="seat touch interface"/>
2101+ </request>
2102+
2103+ <!-- Version 2 additions -->
2104+
2105+ <event name="name" since="2">
2106+ <description summary="unique identifier for this seat">
2107+ In a multi-seat configuration the seat name can be used by clients to
2108+ help identify which physical devices the seat represents.
2109+
2110+ The seat name is a UTF-8 string with no convention defined for its
2111+ contents. Each name is unique among all wl_seat globals. The name is
2112+ only guaranteed to be unique for the current compositor instance.
2113+
2114+ The same seat names are used for all clients. Thus, the name can be
2115+ shared across processes to refer to a specific wl_seat global.
2116+
2117+ The name event is sent after binding to the seat global, and should be sent
2118+ before announcing capabilities. This event is only sent once per seat object,
2119+ and the name does not change over the lifetime of the wl_seat global.
2120+
2121+ Compositors may re-use the same seat name if the wl_seat global is
2122+ destroyed and re-created later.
2123+ </description>
2124+ <arg name="name" type="string" summary="seat identifier"/>
2125+ </event>
2126+
2127+ <!-- Version 5 additions -->
2128+
2129+ <request name="release" type="destructor" since="5">
2130+ <description summary="release the seat object">
2131+ Using this request a client can tell the server that it is not going to
2132+ use the seat object anymore.
2133+ </description>
2134+ </request>
2135+
2136+ </interface>
2137+
2138+ <interface name="wl_pointer" version="11">
2139+ <description summary="pointer input device">
2140+ The wl_pointer interface represents one or more input devices,
2141+ such as mice, which control the pointer location and pointer_focus
2142+ of a seat.
2143+
2144+ The wl_pointer interface generates motion, enter and leave
2145+ events for the surfaces that the pointer is located over,
2146+ and button and axis events for button presses, button releases
2147+ and scrolling.
2148+ </description>
2149+
2150+ <enum name="error">
2151+ <entry name="role" value="0" summary="given wl_surface has another role"/>
2152+ </enum>
2153+
2154+ <request name="set_cursor">
2155+ <description summary="set the pointer surface">
2156+ Set the pointer surface, i.e., the surface that contains the
2157+ pointer image (cursor). This request gives the surface the role
2158+ of a cursor. If the surface already has another role, it raises
2159+ a protocol error.
2160+
2161+ The cursor actually changes only if the pointer
2162+ focus for this device is one of the requesting client's surfaces
2163+ or the surface parameter is the current pointer surface. If
2164+ there was a previous surface set with this request it is
2165+ replaced. If surface is NULL, the pointer image is hidden.
2166+
2167+ The parameters hotspot_x and hotspot_y define the position of
2168+ the pointer surface relative to the pointer location. Its
2169+ top-left corner is always at (x, y) - (hotspot_x, hotspot_y),
2170+ where (x, y) are the coordinates of the pointer location, in
2171+ surface-local coordinates.
2172+
2173+ On wl_surface.offset requests to the pointer surface, hotspot_x
2174+ and hotspot_y are decremented by the x and y parameters
2175+ passed to the request. The offset must be applied by
2176+ wl_surface.commit as usual.
2177+
2178+ The hotspot can also be updated by passing the currently set
2179+ pointer surface to this request with new values for hotspot_x
2180+ and hotspot_y.
2181+
2182+ The input region is ignored for wl_surfaces with the role of
2183+ a cursor. When the use as a cursor ends, the wl_surface is
2184+ unmapped.
2185+
2186+ The serial parameter must match the latest wl_pointer.enter
2187+ serial number sent to the client. Otherwise the request will be
2188+ ignored.
2189+ </description>
2190+ <arg name="serial" type="uint" summary="serial number of the enter event"/>
2191+ <arg name="surface" type="object" interface="wl_surface" allow-null="true"
2192+ summary="pointer surface"/>
2193+ <arg name="hotspot_x" type="int" summary="surface-local x coordinate"/>
2194+ <arg name="hotspot_y" type="int" summary="surface-local y coordinate"/>
2195+ </request>
2196+
2197+ <event name="enter">
2198+ <description summary="enter event">
2199+ Notification that this seat's pointer is focused on a certain
2200+ surface.
2201+
2202+ When a seat's focus enters a surface, the pointer image
2203+ is undefined and a client should respond to this event by setting
2204+ an appropriate pointer image with the set_cursor request.
2205+ </description>
2206+ <arg name="serial" type="uint" summary="serial number of the enter event"/>
2207+ <arg name="surface" type="object" interface="wl_surface" summary="surface entered by the pointer"/>
2208+ <arg name="surface_x" type="fixed" summary="surface-local x coordinate"/>
2209+ <arg name="surface_y" type="fixed" summary="surface-local y coordinate"/>
2210+ </event>
2211+
2212+ <event name="leave">
2213+ <description summary="leave event">
2214+ Notification that this seat's pointer is no longer focused on
2215+ a certain surface.
2216+
2217+ The leave notification is sent before the enter notification
2218+ for the new focus.
2219+ </description>
2220+ <arg name="serial" type="uint" summary="serial number of the leave event"/>
2221+ <arg name="surface" type="object" interface="wl_surface" summary="surface left by the pointer"/>
2222+ </event>
2223+
2224+ <event name="motion">
2225+ <description summary="pointer motion event">
2226+ Notification of pointer location change. The arguments
2227+ surface_x and surface_y are the location relative to the
2228+ focused surface.
2229+ </description>
2230+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
2231+ <arg name="surface_x" type="fixed" summary="surface-local x coordinate"/>
2232+ <arg name="surface_y" type="fixed" summary="surface-local y coordinate"/>
2233+ </event>
2234+
2235+ <enum name="button_state">
2236+ <description summary="physical button state">
2237+ Describes the physical state of a button that produced the button
2238+ event.
2239+ </description>
2240+ <entry name="released" value="0" summary="the button is not pressed"/>
2241+ <entry name="pressed" value="1" summary="the button is pressed"/>
2242+ </enum>
2243+
2244+ <event name="button">
2245+ <description summary="pointer button event">
2246+ Mouse button click and release notifications.
2247+
2248+ The location of the click is given by the last motion, warp or
2249+ enter event.
2250+ The time argument is a timestamp with millisecond
2251+ granularity, with an undefined base.
2252+
2253+ The button is a button code as defined in the Linux kernel's
2254+ linux/input-event-codes.h header file, e.g. BTN_LEFT.
2255+
2256+ Any 16-bit button code value is reserved for future additions to the
2257+ kernel's event code list. All other button codes above 0xFFFF are
2258+ currently undefined but may be used in future versions of this
2259+ protocol.
2260+ </description>
2261+ <arg name="serial" type="uint" summary="serial number of the button event"/>
2262+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
2263+ <arg name="button" type="uint" summary="button that produced the event"/>
2264+ <arg name="state" type="uint" enum="button_state" summary="physical state of the button"/>
2265+ </event>
2266+
2267+ <enum name="axis">
2268+ <description summary="axis types">
2269+ Describes the axis types of scroll events.
2270+ </description>
2271+ <entry name="vertical_scroll" value="0" summary="vertical axis"/>
2272+ <entry name="horizontal_scroll" value="1" summary="horizontal axis"/>
2273+ </enum>
2274+
2275+ <event name="axis">
2276+ <description summary="axis event">
2277+ Scroll and other axis notifications.
2278+
2279+ For scroll events (vertical and horizontal scroll axes), the
2280+ value parameter is the length of a vector along the specified
2281+ axis in a coordinate space identical to those of motion events,
2282+ representing a relative movement along the specified axis.
2283+
2284+ For devices that support movements non-parallel to axes multiple
2285+ axis events will be emitted.
2286+
2287+ When applicable, for example for touch pads, the server can
2288+ choose to emit scroll events where the motion vector is
2289+ equivalent to a motion event vector.
2290+
2291+ When applicable, a client can transform its content relative to the
2292+ scroll distance.
2293+ </description>
2294+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
2295+ <arg name="axis" type="uint" enum="axis" summary="axis type"/>
2296+ <arg name="value" type="fixed" summary="length of vector in surface-local coordinate space"/>
2297+ </event>
2298+
2299+ <!-- Version 3 additions -->
2300+
2301+ <request name="release" type="destructor" since="3">
2302+ <description summary="release the pointer object">
2303+ Using this request a client can tell the server that it is not going to
2304+ use the pointer object anymore.
2305+
2306+ This request destroys the pointer proxy object, so clients must not call
2307+ wl_pointer_destroy() after using this request.
2308+ </description>
2309+ </request>
2310+
2311+ <!-- Version 5 additions -->
2312+
2313+ <event name="frame" since="5">
2314+ <description summary="end of a pointer event sequence">
2315+ Indicates the end of a set of events that logically belong together.
2316+ A client is expected to accumulate the data in all events within the
2317+ frame before proceeding.
2318+
2319+ All wl_pointer events before a wl_pointer.frame event belong
2320+ logically together. For example, in a diagonal scroll motion the
2321+ compositor will send an optional wl_pointer.axis_source event, two
2322+ wl_pointer.axis events (horizontal and vertical) and finally a
2323+ wl_pointer.frame event. The client may use this information to
2324+ calculate a diagonal vector for scrolling.
2325+
2326+ When multiple wl_pointer.axis events occur within the same frame,
2327+ the motion vector is the combined motion of all events.
2328+ When a wl_pointer.axis and a wl_pointer.axis_stop event occur within
2329+ the same frame, this indicates that axis movement in one axis has
2330+ stopped but continues in the other axis.
2331+ When multiple wl_pointer.axis_stop events occur within the same
2332+ frame, this indicates that these axes stopped in the same instance.
2333+
2334+ A wl_pointer.frame event is sent for every logical event group,
2335+ even if the group only contains a single wl_pointer event.
2336+ Specifically, a client may get a sequence: motion, frame, button,
2337+ frame, axis, frame, axis_stop, frame.
2338+
2339+ The wl_pointer.enter and wl_pointer.leave events are logical events
2340+ generated by the compositor and not the hardware. These events are
2341+ also grouped by a wl_pointer.frame. When a pointer moves from one
2342+ surface to another, a compositor should group the
2343+ wl_pointer.leave event within the same wl_pointer.frame.
2344+ However, a client must not rely on wl_pointer.leave and
2345+ wl_pointer.enter being in the same wl_pointer.frame.
2346+ Compositor-specific policies may require the wl_pointer.leave and
2347+ wl_pointer.enter event being split across multiple wl_pointer.frame
2348+ groups.
2349+ </description>
2350+ </event>
2351+
2352+ <enum name="axis_source">
2353+ <description summary="axis source types">
2354+ Describes the source types for axis events. This indicates to the
2355+ client how an axis event was physically generated; a client may
2356+ adjust the user interface accordingly. For example, scroll events
2357+ from a "finger" source may be in a smooth coordinate space with
2358+ kinetic scrolling whereas a "wheel" source may be in discrete steps
2359+ of a number of lines.
2360+
2361+ The "continuous" axis source is a device generating events in a
2362+ continuous coordinate space, but using something other than a
2363+ finger. One example for this source is button-based scrolling where
2364+ the vertical motion of a device is converted to scroll events while
2365+ a button is held down.
2366+
2367+ The "wheel tilt" axis source indicates that the actual device is a
2368+ wheel but the scroll event is not caused by a rotation but a
2369+ (usually sideways) tilt of the wheel.
2370+ </description>
2371+ <entry name="wheel" value="0" summary="a physical wheel rotation" />
2372+ <entry name="finger" value="1" summary="finger on a touch surface" />
2373+ <entry name="continuous" value="2" summary="continuous coordinate space"/>
2374+ <entry name="wheel_tilt" value="3" summary="a physical wheel tilt" since="6"/>
2375+ </enum>
2376+
2377+ <event name="axis_source" since="5">
2378+ <description summary="axis source event">
2379+ Source information for scroll and other axes.
2380+
2381+ This event does not occur on its own. It is sent before a
2382+ wl_pointer.frame event and carries the source information for
2383+ all events within that frame.
2384+
2385+ The source specifies how this event was generated. If the source is
2386+ wl_pointer.axis_source.finger, a wl_pointer.axis_stop event will be
2387+ sent when the user lifts the finger off the device.
2388+
2389+ If the source is wl_pointer.axis_source.wheel,
2390+ wl_pointer.axis_source.wheel_tilt or
2391+ wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event may
2392+ or may not be sent. Whether a compositor sends an axis_stop event
2393+ for these sources is hardware-specific and implementation-dependent;
2394+ clients must not rely on receiving an axis_stop event for these
2395+ scroll sources and should treat scroll sequences from these scroll
2396+ sources as unterminated by default.
2397+
2398+ This event is optional. If the source is unknown for a particular
2399+ axis event sequence, no event is sent.
2400+ Only one wl_pointer.axis_source event is permitted per frame.
2401+
2402+ The order of wl_pointer.axis_discrete and wl_pointer.axis_source is
2403+ not guaranteed.
2404+ </description>
2405+ <arg name="axis_source" type="uint" enum="axis_source" summary="source of the axis event"/>
2406+ </event>
2407+
2408+ <event name="axis_stop" since="5">
2409+ <description summary="axis stop event">
2410+ Stop notification for scroll and other axes.
2411+
2412+ For some wl_pointer.axis_source types, a wl_pointer.axis_stop event
2413+ is sent to notify a client that the axis sequence has terminated.
2414+ This enables the client to implement kinetic scrolling.
2415+ See the wl_pointer.axis_source documentation for information on when
2416+ this event may be generated.
2417+
2418+ Any wl_pointer.axis events with the same axis_source after this
2419+ event should be considered as the start of a new axis motion.
2420+
2421+ The timestamp is to be interpreted identical to the timestamp in the
2422+ wl_pointer.axis event. The timestamp value may be the same as a
2423+ preceding wl_pointer.axis event.
2424+ </description>
2425+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
2426+ <arg name="axis" type="uint" enum="axis" summary="the axis stopped with this event"/>
2427+ </event>
2428+
2429+ <event name="axis_discrete" since="5" deprecated-since="8">
2430+ <description summary="axis click event">
2431+ Discrete step information for scroll and other axes.
2432+
2433+ This event carries the axis value of the wl_pointer.axis event in
2434+ discrete steps (e.g. mouse wheel clicks).
2435+
2436+ This event is deprecated with wl_pointer version 8 - this event is not
2437+ sent to clients supporting version 8 or later.
2438+
2439+ This event does not occur on its own, it is coupled with a
2440+ wl_pointer.axis event that represents this axis value on a
2441+ continuous scale. The protocol guarantees that each axis_discrete
2442+ event is always followed by exactly one axis event with the same
2443+ axis number within the same wl_pointer.frame. Note that the protocol
2444+ allows for other events to occur between the axis_discrete and
2445+ its coupled axis event, including other axis_discrete or axis
2446+ events. A wl_pointer.frame must not contain more than one axis_discrete
2447+ event per axis type.
2448+
2449+ This event is optional; continuous scrolling devices
2450+ like two-finger scrolling on touchpads do not have discrete
2451+ steps and do not generate this event.
2452+
2453+ The discrete value carries the directional information. e.g. a value
2454+ of -2 is two steps towards the negative direction of this axis.
2455+
2456+ The axis number is identical to the axis number in the associated
2457+ axis event.
2458+
2459+ The order of wl_pointer.axis_discrete and wl_pointer.axis_source is
2460+ not guaranteed.
2461+ </description>
2462+ <arg name="axis" type="uint" enum="axis" summary="axis type"/>
2463+ <arg name="discrete" type="int" summary="number of steps"/>
2464+ </event>
2465+
2466+ <event name="axis_value120" since="8">
2467+ <description summary="axis high-resolution scroll event">
2468+ Discrete high-resolution scroll information.
2469+
2470+ This event carries high-resolution wheel scroll information,
2471+ with each multiple of 120 representing one logical scroll step
2472+ (a wheel detent). For example, an axis_value120 of 30 is one quarter of
2473+ a logical scroll step in the positive direction, a value120 of
2474+ -240 are two logical scroll steps in the negative direction within the
2475+ same hardware event.
2476+ Clients that rely on discrete scrolling should accumulate the
2477+ value120 to multiples of 120 before processing the event.
2478+
2479+ The value120 must not be zero.
2480+
2481+ This event replaces the wl_pointer.axis_discrete event in clients
2482+ supporting wl_pointer version 8 or later.
2483+
2484+ Where a wl_pointer.axis_source event occurs in the same
2485+ wl_pointer.frame, the axis source applies to this event.
2486+
2487+ The order of wl_pointer.axis_value120 and wl_pointer.axis_source is
2488+ not guaranteed.
2489+ </description>
2490+ <arg name="axis" type="uint" enum="axis" summary="axis type"/>
2491+ <arg name="value120" type="int" summary="scroll distance as fraction of 120"/>
2492+ </event>
2493+
2494+ <!-- Version 9 additions -->
2495+
2496+ <enum name="axis_relative_direction">
2497+ <description summary="axis relative direction">
2498+ This specifies the direction of the physical motion that caused a
2499+ wl_pointer.axis event, relative to the wl_pointer.axis direction.
2500+ </description>
2501+ <entry name="identical" value="0"
2502+ summary="physical motion matches axis direction"/>
2503+ <entry name="inverted" value="1"
2504+ summary="physical motion is the inverse of the axis direction"/>
2505+ </enum>
2506+
2507+ <event name="axis_relative_direction" since="9">
2508+ <description summary="axis relative physical direction event">
2509+ Relative directional information of the entity causing the axis
2510+ motion.
2511+
2512+ For a wl_pointer.axis event, the wl_pointer.axis_relative_direction
2513+ event specifies the movement direction of the entity causing the
2514+ wl_pointer.axis event. For example:
2515+ - if a user's fingers on a touchpad move down and this
2516+ causes a wl_pointer.axis vertical_scroll down event, the physical
2517+ direction is 'identical'
2518+ - if a user's fingers on a touchpad move down and this causes a
2519+ wl_pointer.axis vertical_scroll up scroll up event ('natural
2520+ scrolling'), the physical direction is 'inverted'.
2521+
2522+ A client may use this information to adjust scroll motion of
2523+ components. Specifically, enabling natural scrolling causes the
2524+ content to change direction compared to traditional scrolling.
2525+ Some widgets like volume control sliders should usually match the
2526+ physical direction regardless of whether natural scrolling is
2527+ active. This event enables clients to match the scroll direction of
2528+ a widget to the physical direction.
2529+
2530+ This event does not occur on its own, it is coupled with a
2531+ wl_pointer.axis event that represents this axis value.
2532+ The protocol guarantees that each axis_relative_direction event is
2533+ always followed by exactly one axis event with the same
2534+ axis number within the same wl_pointer.frame. Note that the protocol
2535+ allows for other events to occur between the axis_relative_direction
2536+ and its coupled axis event.
2537+
2538+ The axis number is identical to the axis number in the associated
2539+ axis event.
2540+
2541+ The order of wl_pointer.axis_relative_direction,
2542+ wl_pointer.axis_discrete and wl_pointer.axis_source is not
2543+ guaranteed.
2544+ </description>
2545+ <arg name="axis" type="uint" enum="axis" summary="axis type"/>
2546+ <arg name="direction" type="uint" enum="axis_relative_direction"
2547+ summary="physical direction relative to axis motion"/>
2548+ </event>
2549+
2550+ <!-- Version 11 additions -->
2551+
2552+ <event name="warp" since="11">
2553+ <description summary="pointer warp event">
2554+ Notification of pointer location change within a surface.
2555+
2556+ This location change is not due to events on the input device,
2557+ but because either the surface under the pointer was moved and
2558+ thus the relative position of the pointer changed, or because
2559+ the compositor changed the pointer position in response to an
2560+ event like pointer confinement being exited.
2561+
2562+ The arguments surface_x and surface_y are the location relative to
2563+ the focused surface.
2564+
2565+ This event must not occur in the same wl_pointer.frame as a
2566+ wl_pointer.enter or wl_pointer.motion event.
2567+ </description>
2568+ <arg name="surface_x" type="fixed" summary="surface-local x coordinate"/>
2569+ <arg name="surface_y" type="fixed" summary="surface-local y coordinate"/>
2570+ </event>
2571+ </interface>
2572+
2573+ <interface name="wl_keyboard" version="11">
2574+ <description summary="keyboard input device">
2575+ The wl_keyboard interface represents one or more keyboards
2576+ associated with a seat.
2577+
2578+ Each wl_keyboard has the following logical state:
2579+
2580+ - an active surface (possibly null),
2581+ - the keys currently logically down,
2582+ - the active modifiers,
2583+ - the active group.
2584+
2585+ By default, the active surface is null, the keys currently logically down
2586+ are empty, the active modifiers and the active group are 0.
2587+ </description>
2588+
2589+ <enum name="keymap_format">
2590+ <description summary="keyboard mapping format">
2591+ This specifies the format of the keymap provided to the
2592+ client with the wl_keyboard.keymap event.
2593+ </description>
2594+ <entry name="no_keymap" value="0"
2595+ summary="no keymap; client must understand how to interpret the raw keycode"/>
2596+ <entry name="xkb_v1" value="1"
2597+ summary="libxkbcommon compatible, null-terminated string; to determine the xkb keycode, clients must add 8 to the key event keycode"/>
2598+ </enum>
2599+
2600+ <event name="keymap">
2601+ <description summary="keyboard mapping">
2602+ This event provides a file descriptor to the client which can be
2603+ memory-mapped in read-only mode to provide a keyboard mapping
2604+ description.
2605+
2606+ From version 7 onwards, the fd must be mapped with MAP_PRIVATE by
2607+ the recipient, as MAP_SHARED may fail.
2608+ </description>
2609+ <arg name="format" type="uint" enum="keymap_format" summary="keymap format"/>
2610+ <arg name="fd" type="fd" summary="keymap file descriptor"/>
2611+ <arg name="size" type="uint" summary="keymap size, in bytes"/>
2612+ </event>
2613+
2614+ <event name="enter">
2615+ <description summary="enter event">
2616+ Notification that this seat's keyboard focus is on a certain
2617+ surface.
2618+
2619+ The compositor must send the wl_keyboard.modifiers event after this
2620+ event.
2621+
2622+ In the wl_keyboard logical state, this event sets the active surface to
2623+ the surface argument and the keys currently logically down to the keys
2624+ in the keys argument. The compositor must not send this event if the
2625+ wl_keyboard already had an active surface immediately before this event.
2626+
2627+ Clients should not use the list of pressed keys to emulate key-press
2628+ events. The order of keys in the list is unspecified.
2629+ </description>
2630+ <arg name="serial" type="uint" summary="serial number of the enter event"/>
2631+ <arg name="surface" type="object" interface="wl_surface" summary="surface gaining keyboard focus"/>
2632+ <arg name="keys" type="array" summary="the keys currently logically down"/>
2633+ </event>
2634+
2635+ <event name="leave">
2636+ <description summary="leave event">
2637+ Notification that this seat's keyboard focus is no longer on
2638+ a certain surface.
2639+
2640+ The leave notification is sent before the enter notification
2641+ for the new focus.
2642+
2643+ In the wl_keyboard logical state, this event resets all values to their
2644+ defaults. The compositor must not send this event if the active surface
2645+ of the wl_keyboard was not equal to the surface argument immediately
2646+ before this event.
2647+ </description>
2648+ <arg name="serial" type="uint" summary="serial number of the leave event"/>
2649+ <arg name="surface" type="object" interface="wl_surface" summary="surface that lost keyboard focus"/>
2650+ </event>
2651+
2652+ <enum name="key_state">
2653+ <description summary="physical key state">
2654+ Describes the physical state of a key that produced the key event.
2655+
2656+ Since version 10, the key can be in a "repeated" pseudo-state which
2657+ means the same as "pressed", but is used to signal repetition in the
2658+ key event.
2659+
2660+ The key may only enter the repeated state after entering the pressed
2661+ state and before entering the released state. This event may be
2662+ generated multiple times while the key is down.
2663+ </description>
2664+ <entry name="released" value="0" summary="key is not pressed"/>
2665+ <entry name="pressed" value="1" summary="key is pressed"/>
2666+ <entry name="repeated" value="2" summary="key was repeated" since="10"/>
2667+ </enum>
2668+
2669+ <event name="key">
2670+ <description summary="key event">
2671+ A key was pressed or released.
2672+ The time argument is a timestamp with millisecond
2673+ granularity, with an undefined base.
2674+
2675+ The key is a platform-specific key code that can be interpreted
2676+ by feeding it to the keyboard mapping (see the keymap event).
2677+
2678+ If this event produces a change in modifiers, then the resulting
2679+ wl_keyboard.modifiers event must be sent after this event.
2680+
2681+ In the wl_keyboard logical state, this event adds the key to the keys
2682+ currently logically down (if the state argument is pressed) or removes
2683+ the key from the keys currently logically down (if the state argument is
2684+ released). The compositor must not send this event if the wl_keyboard
2685+ did not have an active surface immediately before this event. The
2686+ compositor must not send this event if state is pressed (resp. released)
2687+ and the key was already logically down (resp. was not logically down)
2688+ immediately before this event.
2689+
2690+ Since version 10, compositors may send key events with the "repeated"
2691+ key state when a wl_keyboard.repeat_info event with a rate argument of
2692+ 0 has been received. This allows the compositor to take over the
2693+ responsibility of key repetition.
2694+ </description>
2695+ <arg name="serial" type="uint" summary="serial number of the key event"/>
2696+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
2697+ <arg name="key" type="uint" summary="key that produced the event"/>
2698+ <arg name="state" type="uint" enum="key_state" summary="physical state of the key"/>
2699+ </event>
2700+
2701+ <event name="modifiers">
2702+ <description summary="modifier and group state">
2703+ Notifies clients that the modifier and/or group state has
2704+ changed, and it should update its local state.
2705+
2706+ The compositor may send this event without a surface of the client
2707+ having keyboard focus, for example to tie modifier information to
2708+ pointer focus instead. If a modifier event with pressed modifiers is sent
2709+ without a prior enter event, the client can assume the modifier state is
2710+ valid until it receives the next wl_keyboard.modifiers event. In order to
2711+ reset the modifier state again, the compositor can send a
2712+ wl_keyboard.modifiers event with no pressed modifiers.
2713+
2714+ In the wl_keyboard logical state, this event updates the modifiers and
2715+ group.
2716+ </description>
2717+ <arg name="serial" type="uint" summary="serial number of the modifiers event"/>
2718+ <arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
2719+ <arg name="mods_latched" type="uint" summary="latched modifiers"/>
2720+ <arg name="mods_locked" type="uint" summary="locked modifiers"/>
2721+ <arg name="group" type="uint" summary="keyboard layout"/>
2722+ </event>
2723+
2724+ <!-- Version 3 additions -->
2725+
2726+ <request name="release" type="destructor" since="3">
2727+ <description summary="release the keyboard object"/>
2728+ </request>
2729+
2730+ <!-- Version 4 additions -->
2731+
2732+ <event name="repeat_info" since="4">
2733+ <description summary="repeat rate and delay">
2734+ Informs the client about the keyboard's repeat rate and delay.
2735+
2736+ This event is sent as soon as the wl_keyboard object has been created,
2737+ and is guaranteed to be received by the client before any key press
2738+ event.
2739+
2740+ Negative values for either rate or delay are illegal. A rate of zero
2741+ will disable any repeating (regardless of the value of delay).
2742+
2743+ This event can be sent later on as well with a new value if necessary,
2744+ so clients should continue listening for the event past the creation
2745+ of wl_keyboard.
2746+ </description>
2747+ <arg name="rate" type="int"
2748+ summary="the rate of repeating keys in characters per second"/>
2749+ <arg name="delay" type="int"
2750+ summary="delay in milliseconds since key down until repeating starts"/>
2751+ </event>
2752+ </interface>
2753+
2754+ <interface name="wl_touch" version="11">
2755+ <description summary="touchscreen input device">
2756+ The wl_touch interface represents a touchscreen
2757+ associated with a seat.
2758+
2759+ Touch interactions can consist of one or more contacts.
2760+ For each contact, a series of events is generated, starting
2761+ with a down event, followed by zero or more motion events,
2762+ and ending with an up event. Events relating to the same
2763+ contact point can be identified by the ID of the sequence.
2764+ </description>
2765+
2766+ <event name="down">
2767+ <description summary="touch down event and beginning of a touch sequence">
2768+ A new touch point has appeared on the surface. This touch point is
2769+ assigned a unique ID. Future events from this touch point reference
2770+ this ID. The ID ceases to be valid after a touch up event and may be
2771+ reused in the future.
2772+ </description>
2773+ <arg name="serial" type="uint" summary="serial number of the touch down event"/>
2774+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
2775+ <arg name="surface" type="object" interface="wl_surface" summary="surface touched"/>
2776+ <arg name="id" type="int" summary="the unique ID of this touch point"/>
2777+ <arg name="x" type="fixed" summary="surface-local x coordinate"/>
2778+ <arg name="y" type="fixed" summary="surface-local y coordinate"/>
2779+ </event>
2780+
2781+ <event name="up">
2782+ <description summary="end of a touch event sequence">
2783+ The touch point has disappeared. No further events will be sent for
2784+ this touch point and the touch point's ID is released and may be
2785+ reused in a future touch down event.
2786+ </description>
2787+ <arg name="serial" type="uint" summary="serial number of the touch up event"/>
2788+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
2789+ <arg name="id" type="int" summary="the unique ID of this touch point"/>
2790+ </event>
2791+
2792+ <event name="motion">
2793+ <description summary="update of touch point coordinates">
2794+ A touch point has changed coordinates.
2795+ </description>
2796+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
2797+ <arg name="id" type="int" summary="the unique ID of this touch point"/>
2798+ <arg name="x" type="fixed" summary="surface-local x coordinate"/>
2799+ <arg name="y" type="fixed" summary="surface-local y coordinate"/>
2800+ </event>
2801+
2802+ <event name="frame">
2803+ <description summary="end of touch frame event">
2804+ Indicates the end of a set of events that logically belong together.
2805+ A client is expected to accumulate the data in all events within the
2806+ frame before proceeding.
2807+
2808+ A wl_touch.frame terminates at least one event but otherwise no
2809+ guarantee is provided about the set of events within a frame. A client
2810+ must assume that any state not updated in a frame is unchanged from the
2811+ previously known state.
2812+ </description>
2813+ </event>
2814+
2815+ <event name="cancel">
2816+ <description summary="touch session cancelled">
2817+ Sent if the compositor decides the touch stream is a global
2818+ gesture. No further events are sent to the clients from that
2819+ particular gesture. Touch cancellation applies to all touch points
2820+ currently active on this client's surface. The client is
2821+ responsible for finalizing the touch points, future touch points on
2822+ this surface may reuse the touch point ID.
2823+
2824+ No frame event is required after the cancel event.
2825+ </description>
2826+ </event>
2827+
2828+ <!-- Version 3 additions -->
2829+
2830+ <request name="release" type="destructor" since="3">
2831+ <description summary="release the touch object"/>
2832+ </request>
2833+
2834+ <!-- Version 6 additions -->
2835+
2836+ <event name="shape" since="6">
2837+ <description summary="update shape of touch point">
2838+ Sent when a touchpoint has changed its shape.
2839+
2840+ This event does not occur on its own. It is sent before a
2841+ wl_touch.frame event and carries the new shape information for
2842+ any previously reported, or new touch points of that frame.
2843+
2844+ Other events describing the touch point such as wl_touch.down,
2845+ wl_touch.motion or wl_touch.orientation may be sent within the
2846+ same wl_touch.frame. A client should treat these events as a single
2847+ logical touch point update. The order of wl_touch.shape,
2848+ wl_touch.orientation and wl_touch.motion is not guaranteed.
2849+ A wl_touch.down event is guaranteed to occur before the first
2850+ wl_touch.shape event for this touch ID but both events may occur within
2851+ the same wl_touch.frame.
2852+
2853+ A touchpoint shape is approximated by an ellipse through the major and
2854+ minor axis length. The major axis length describes the longer diameter
2855+ of the ellipse, while the minor axis length describes the shorter
2856+ diameter. Major and minor are orthogonal and both are specified in
2857+ surface-local coordinates. The center of the ellipse is always at the
2858+ touchpoint location as reported by wl_touch.down or wl_touch.motion.
2859+
2860+ This event is only sent by the compositor if the touch device supports
2861+ shape reports. The client has to make reasonable assumptions about the
2862+ shape if it did not receive this event.
2863+ </description>
2864+ <arg name="id" type="int" summary="the unique ID of this touch point"/>
2865+ <arg name="major" type="fixed" summary="length of the major axis in surface-local coordinates"/>
2866+ <arg name="minor" type="fixed" summary="length of the minor axis in surface-local coordinates"/>
2867+ </event>
2868+
2869+ <event name="orientation" since="6">
2870+ <description summary="update orientation of touch point">
2871+ Sent when a touchpoint has changed its orientation.
2872+
2873+ This event does not occur on its own. It is sent before a
2874+ wl_touch.frame event and carries the new shape information for
2875+ any previously reported, or new touch points of that frame.
2876+
2877+ Other events describing the touch point such as wl_touch.down,
2878+ wl_touch.motion or wl_touch.shape may be sent within the
2879+ same wl_touch.frame. A client should treat these events as a single
2880+ logical touch point update. The order of wl_touch.shape,
2881+ wl_touch.orientation and wl_touch.motion is not guaranteed.
2882+ A wl_touch.down event is guaranteed to occur before the first
2883+ wl_touch.orientation event for this touch ID but both events may occur
2884+ within the same wl_touch.frame.
2885+
2886+ The orientation describes the clockwise angle of a touchpoint's major
2887+ axis to the positive surface y-axis and is normalized to the -180 to
2888+ +180 degree range. The granularity of orientation depends on the touch
2889+ device, some devices only support binary rotation values between 0 and
2890+ 90 degrees.
2891+
2892+ This event is only sent by the compositor if the touch device supports
2893+ orientation reports.
2894+ </description>
2895+ <arg name="id" type="int" summary="the unique ID of this touch point"/>
2896+ <arg name="orientation" type="fixed" summary="angle between major axis and positive surface y-axis in degrees"/>
2897+ </event>
2898+ </interface>
2899+
2900+ <interface name="wl_output" version="4">
2901+ <description summary="compositor output region">
2902+ An output describes part of the compositor geometry. The
2903+ compositor works in the 'compositor coordinate system' and an
2904+ output corresponds to a rectangular area in that space that is
2905+ actually visible. This typically corresponds to a monitor that
2906+ displays part of the compositor space. This object is published
2907+ as global during start up, or when a monitor is hotplugged.
2908+ </description>
2909+
2910+ <enum name="subpixel">
2911+ <description summary="subpixel geometry information">
2912+ This enumeration describes how the physical
2913+ pixels on an output are laid out.
2914+ </description>
2915+ <entry name="unknown" value="0" summary="unknown geometry"/>
2916+ <entry name="none" value="1" summary="no geometry"/>
2917+ <entry name="horizontal_rgb" value="2" summary="horizontal RGB"/>
2918+ <entry name="horizontal_bgr" value="3" summary="horizontal BGR"/>
2919+ <entry name="vertical_rgb" value="4" summary="vertical RGB"/>
2920+ <entry name="vertical_bgr" value="5" summary="vertical BGR"/>
2921+ </enum>
2922+
2923+ <enum name="transform">
2924+ <description summary="transformation applied to buffer contents">
2925+ This describes transformations that clients and compositors apply to
2926+ buffer contents.
2927+
2928+ The flipped values correspond to an initial flip around a
2929+ vertical axis followed by rotation.
2930+
2931+ The purpose is mainly to allow clients to render accordingly and
2932+ tell the compositor, so that for fullscreen surfaces, the
2933+ compositor will still be able to scan out directly from client
2934+ surfaces.
2935+ </description>
2936+ <entry name="normal" value="0" summary="no transform"/>
2937+ <entry name="90" value="1" summary="90 degrees counter-clockwise"/>
2938+ <entry name="180" value="2" summary="180 degrees counter-clockwise"/>
2939+ <entry name="270" value="3" summary="270 degrees counter-clockwise"/>
2940+ <entry name="flipped" value="4" summary="180 degree flip around a vertical axis"/>
2941+ <entry name="flipped_90" value="5" summary="flip and rotate 90 degrees counter-clockwise"/>
2942+ <entry name="flipped_180" value="6" summary="flip and rotate 180 degrees counter-clockwise"/>
2943+ <entry name="flipped_270" value="7" summary="flip and rotate 270 degrees counter-clockwise"/>
2944+ </enum>
2945+
2946+ <event name="geometry">
2947+ <description summary="properties of the output">
2948+ The geometry event describes geometric properties of the output.
2949+ The event is sent when binding to the output object and whenever
2950+ any of the properties change.
2951+
2952+ The physical size can be set to zero if it doesn't make sense for this
2953+ output (e.g. for projectors or virtual outputs).
2954+
2955+ The geometry event will be followed by a done event (starting from
2956+ version 2).
2957+
2958+ Clients should use wl_surface.preferred_buffer_transform instead of the
2959+ transform advertised by this event to find the preferred buffer
2960+ transform to use for a surface.
2961+
2962+ Note: wl_output only advertises partial information about the output
2963+ position and identification. Some compositors, for instance those not
2964+ implementing a desktop-style output layout or those exposing virtual
2965+ outputs, might fake this information. Instead of using x and y, clients
2966+ should use xdg_output.logical_position. Instead of using make and model,
2967+ clients should use name and description.
2968+ </description>
2969+ <arg name="x" type="int"
2970+ summary="x position within the global compositor space"/>
2971+ <arg name="y" type="int"
2972+ summary="y position within the global compositor space"/>
2973+ <arg name="physical_width" type="int"
2974+ summary="width in millimeters of the output"/>
2975+ <arg name="physical_height" type="int"
2976+ summary="height in millimeters of the output"/>
2977+ <arg name="subpixel" type="int" enum="subpixel"
2978+ summary="subpixel orientation of the output"/>
2979+ <arg name="make" type="string"
2980+ summary="textual description of the manufacturer"/>
2981+ <arg name="model" type="string"
2982+ summary="textual description of the model"/>
2983+ <arg name="transform" type="int" enum="transform"
2984+ summary="additional transformation applied to buffer contents during presentation"/>
2985+ </event>
2986+
2987+ <enum name="mode" bitfield="true">
2988+ <description summary="mode information">
2989+ These flags describe properties of an output mode.
2990+ They are used in the flags bitfield of the mode event.
2991+ </description>
2992+ <entry name="current" value="0x1"
2993+ summary="indicates this is the current mode"/>
2994+ <entry name="preferred" value="0x2"
2995+ summary="indicates this is the preferred mode"/>
2996+ </enum>
2997+
2998+ <event name="mode">
2999+ <description summary="advertise available modes for the output">
3000+ The mode event describes an available mode for the output.
3001+
3002+ The event is sent when binding to the output object and there
3003+ will always be one mode, the current mode. The event is sent
3004+ again if an output changes mode, for the mode that is now
3005+ current. In other words, the current mode is always the last
3006+ mode that was received with the current flag set.
3007+
3008+ Non-current modes are deprecated. A compositor can decide to only
3009+ advertise the current mode and never send other modes. Clients
3010+ should not rely on non-current modes.
3011+
3012+ The size of a mode is given in physical hardware units of
3013+ the output device. This is not necessarily the same as
3014+ the output size in the global compositor space. For instance,
3015+ the output may be scaled, as described in wl_output.scale,
3016+ or transformed, as described in wl_output.transform. Clients
3017+ willing to retrieve the output size in the global compositor
3018+ space should use xdg_output.logical_size instead.
3019+
3020+ The vertical refresh rate can be set to zero if it doesn't make
3021+ sense for this output (e.g. for virtual outputs).
3022+
3023+ The mode event will be followed by a done event (starting from
3024+ version 2).
3025+
3026+ Clients should not use the refresh rate to schedule frames. Instead,
3027+ they should use the wl_surface.frame event or the presentation-time
3028+ protocol.
3029+
3030+ Note: this information is not always meaningful for all outputs. Some
3031+ compositors, such as those exposing virtual outputs, might fake the
3032+ refresh rate or the size.
3033+ </description>
3034+ <arg name="flags" type="uint" enum="mode" summary="bitfield of mode flags"/>
3035+ <arg name="width" type="int" summary="width of the mode in hardware units"/>
3036+ <arg name="height" type="int" summary="height of the mode in hardware units"/>
3037+ <arg name="refresh" type="int" summary="vertical refresh rate in mHz"/>
3038+ </event>
3039+
3040+ <!-- Version 2 additions -->
3041+
3042+ <event name="done" since="2">
3043+ <description summary="sent all information about output">
3044+ This event is sent after all other properties have been
3045+ sent after binding to the output object and after any
3046+ other property changes done after that. This allows
3047+ changes to the output properties to be seen as
3048+ atomic, even if they happen via multiple events.
3049+ </description>
3050+ </event>
3051+
3052+ <event name="scale" since="2">
3053+ <description summary="output scaling properties">
3054+ This event contains scaling geometry information
3055+ that is not in the geometry event. It may be sent after
3056+ binding the output object or if the output scale changes
3057+ later. The compositor will emit a non-zero, positive
3058+ value for scale. If it is not sent, the client should
3059+ assume a scale of 1.
3060+
3061+ A scale larger than 1 means that the compositor will
3062+ automatically scale surface buffers by this amount
3063+ when rendering. This is used for very high resolution
3064+ displays where applications rendering at the native
3065+ resolution would be too small to be legible.
3066+
3067+ Clients should use wl_surface.preferred_buffer_scale
3068+ instead of this event to find the preferred buffer
3069+ scale to use for a surface.
3070+
3071+ The scale event will be followed by a done event.
3072+ </description>
3073+ <arg name="factor" type="int" summary="scaling factor of output"/>
3074+ </event>
3075+
3076+ <!-- Version 3 additions -->
3077+
3078+ <request name="release" type="destructor" since="3">
3079+ <description summary="release the output object">
3080+ Using this request a client can tell the server that it is not going to
3081+ use the output object anymore.
3082+ </description>
3083+ </request>
3084+
3085+ <!-- Version 4 additions -->
3086+
3087+ <event name="name" since="4">
3088+ <description summary="name of this output">
3089+ Many compositors will assign user-friendly names to their outputs, show
3090+ them to the user, allow the user to refer to an output, etc. The client
3091+ may wish to know this name as well to offer the user similar behaviors.
3092+
3093+ The name is a UTF-8 string with no convention defined for its contents.
3094+ Each name is unique among all wl_output globals. The name is only
3095+ guaranteed to be unique for the compositor instance.
3096+
3097+ The same output name is used for all clients for a given wl_output
3098+ global. Thus, the name can be shared across processes to refer to a
3099+ specific wl_output global.
3100+
3101+ The name is not guaranteed to be persistent across sessions, thus cannot
3102+ be used to reliably identify an output in e.g. configuration files.
3103+
3104+ Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
3105+ not assume that the name is a reflection of an underlying DRM connector,
3106+ X11 connection, etc.
3107+
3108+ The name event is sent after binding the output object. This event is
3109+ only sent once per output object, and the name does not change over the
3110+ lifetime of the wl_output global.
3111+
3112+ Compositors may re-use the same output name if the wl_output global is
3113+ destroyed and re-created later. Compositors should avoid re-using the
3114+ same name if possible.
3115+
3116+ The name event will be followed by a done event.
3117+ </description>
3118+ <arg name="name" type="string" summary="output name"/>
3119+ </event>
3120+
3121+ <event name="description" since="4">
3122+ <description summary="human-readable description of this output">
3123+ Many compositors can produce human-readable descriptions of their
3124+ outputs. The client may wish to know this description as well, e.g. for
3125+ output selection purposes.
3126+
3127+ The description is a UTF-8 string with no convention defined for its
3128+ contents. The description is not guaranteed to be unique among all
3129+ wl_output globals. Examples might include 'Foocorp 11" Display' or
3130+ 'Virtual X11 output via :1'.
3131+
3132+ The description event is sent after binding the output object and
3133+ whenever the description changes. The description is optional, and may
3134+ not be sent at all.
3135+
3136+ The description event will be followed by a done event.
3137+ </description>
3138+ <arg name="description" type="string" summary="output description"/>
3139+ </event>
3140+ </interface>
3141+
3142+ <interface name="wl_region" version="7">
3143+ <description summary="region interface">
3144+ A region object describes an area.
3145+
3146+ Region objects are used to describe the opaque and input
3147+ regions of a surface.
3148+ </description>
3149+
3150+ <request name="destroy" type="destructor">
3151+ <description summary="destroy region">
3152+ Destroy the region. This will invalidate the object ID.
3153+ </description>
3154+ </request>
3155+
3156+ <request name="add">
3157+ <description summary="add rectangle to region">
3158+ Add the specified rectangle to the region.
3159+ </description>
3160+ <arg name="x" type="int" summary="region-local x coordinate"/>
3161+ <arg name="y" type="int" summary="region-local y coordinate"/>
3162+ <arg name="width" type="int" summary="rectangle width"/>
3163+ <arg name="height" type="int" summary="rectangle height"/>
3164+ </request>
3165+
3166+ <request name="subtract">
3167+ <description summary="subtract rectangle from region">
3168+ Subtract the specified rectangle from the region.
3169+ </description>
3170+ <arg name="x" type="int" summary="region-local x coordinate"/>
3171+ <arg name="y" type="int" summary="region-local y coordinate"/>
3172+ <arg name="width" type="int" summary="rectangle width"/>
3173+ <arg name="height" type="int" summary="rectangle height"/>
3174+ </request>
3175+ </interface>
3176+
3177+ <interface name="wl_subcompositor" version="1">
3178+ <description summary="sub-surface compositing">
3179+ The global interface exposing sub-surface compositing capabilities.
3180+ A wl_surface, that has sub-surfaces associated, is called the
3181+ parent surface. Sub-surfaces can be arbitrarily nested and create
3182+ a tree of sub-surfaces.
3183+
3184+ The root surface in a tree of sub-surfaces is the main
3185+ surface. The main surface cannot be a sub-surface, because
3186+ sub-surfaces must always have a parent.
3187+
3188+ A main surface with its sub-surfaces forms a (compound) window.
3189+ For window management purposes, this set of wl_surface objects is
3190+ to be considered as a single window, and it should also behave as
3191+ such.
3192+
3193+ The aim of sub-surfaces is to offload some of the compositing work
3194+ within a window from clients to the compositor. A prime example is
3195+ a video player with decorations and video in separate wl_surface
3196+ objects. This should allow the compositor to pass YUV video buffer
3197+ processing to dedicated overlay hardware when possible.
3198+ </description>
3199+
3200+ <request name="destroy" type="destructor">
3201+ <description summary="unbind from the subcompositor interface">
3202+ Informs the server that the client will not be using this
3203+ protocol object anymore. This does not affect any other
3204+ objects, wl_subsurface objects included.
3205+ </description>
3206+ </request>
3207+
3208+ <enum name="error">
3209+ <entry name="bad_surface" value="0"
3210+ summary="the to-be sub-surface is invalid"/>
3211+ <entry name="bad_parent" value="1"
3212+ summary="the to-be sub-surface parent is invalid"/>
3213+ </enum>
3214+
3215+ <request name="get_subsurface">
3216+ <description summary="give a surface the role sub-surface">
3217+ Create a sub-surface interface for the given surface, and
3218+ associate it with the given parent surface. This turns a
3219+ plain wl_surface into a sub-surface.
3220+
3221+ The to-be sub-surface must not already have another role, and it
3222+ must not have an existing wl_subsurface object. Otherwise the
3223+ bad_surface protocol error is raised.
3224+
3225+ Adding sub-surfaces to a parent is a double-buffered operation on the
3226+ parent (see wl_surface.commit). The effect of adding a sub-surface
3227+ becomes visible on the next time the state of the parent surface is
3228+ applied.
3229+
3230+ The parent surface must not be one of the child surface's descendants,
3231+ and the parent must be different from the child surface, otherwise the
3232+ bad_parent protocol error is raised.
3233+
3234+ This request modifies the behaviour of wl_surface.commit request on
3235+ the sub-surface, see the documentation on wl_subsurface interface.
3236+ </description>
3237+ <arg name="id" type="new_id" interface="wl_subsurface"
3238+ summary="the new sub-surface object ID"/>
3239+ <arg name="surface" type="object" interface="wl_surface"
3240+ summary="the surface to be turned into a sub-surface"/>
3241+ <arg name="parent" type="object" interface="wl_surface"
3242+ summary="the parent surface"/>
3243+ </request>
3244+ </interface>
3245+
3246+ <interface name="wl_subsurface" version="1">
3247+ <description summary="sub-surface interface to a wl_surface">
3248+ An additional interface to a wl_surface object, which has been
3249+ made a sub-surface. A sub-surface has one parent surface. A
3250+ sub-surface's size and position are not limited to that of the parent.
3251+ Particularly, a sub-surface is not automatically clipped to its
3252+ parent's area.
3253+
3254+ A sub-surface becomes mapped, when a non-NULL wl_buffer is applied
3255+ and the parent surface is mapped. The order of which one happens
3256+ first is irrelevant. A sub-surface is hidden if the parent becomes
3257+ hidden, or if a NULL wl_buffer is applied. These rules apply
3258+ recursively through the tree of surfaces.
3259+
3260+ A sub-surface can be in one of two modes. The possible modes are
3261+ synchronized and desynchronized, see methods wl_subsurface.set_sync and
3262+ wl_subsurface.set_desync.
3263+
3264+ The main surface can be thought to be always in desynchronized mode,
3265+ since it does not have a parent in the sub-surfaces sense.
3266+
3267+ Even if a sub-surface is in desynchronized mode, it will behave as
3268+ in synchronized mode, if its parent surface behaves as in
3269+ synchronized mode. This rule is applied recursively throughout the
3270+ tree of surfaces. This means, that one can set a sub-surface into
3271+ synchronized mode, and then assume that all its child and grand-child
3272+ sub-surfaces are synchronized, too, without explicitly setting them.
3273+
3274+ If a surface behaves as in synchronized mode, it is effectively
3275+ synchronized, otherwise it is effectively desynchronized.
3276+
3277+ A sub-surface is initially in the synchronized mode.
3278+
3279+ The wl_subsurface interface has requests which modify double-buffered
3280+ state of the parent surface (wl_subsurface.set_position, .place_above and
3281+ .place_below).
3282+
3283+ Destroying a sub-surface takes effect immediately. If you need to
3284+ synchronize the removal of a sub-surface to the parent surface update,
3285+ unmap the sub-surface first by attaching a NULL wl_buffer, update parent,
3286+ and then destroy the sub-surface.
3287+
3288+ If the parent wl_surface object is destroyed, the sub-surface is
3289+ unmapped.
3290+
3291+ A sub-surface never has the keyboard focus of any seat.
3292+
3293+ The wl_surface.offset request is ignored: clients must use set_position
3294+ instead to move the sub-surface.
3295+ </description>
3296+
3297+ <request name="destroy" type="destructor">
3298+ <description summary="remove sub-surface interface">
3299+ The sub-surface interface is removed from the wl_surface object
3300+ that was turned into a sub-surface with a
3301+ wl_subcompositor.get_subsurface request. The wl_surface's association
3302+ to the parent is deleted. The wl_surface is unmapped immediately.
3303+ </description>
3304+ </request>
3305+
3306+ <enum name="error">
3307+ <entry name="bad_surface" value="0"
3308+ summary="wl_surface is not a sibling or the parent"/>
3309+ </enum>
3310+
3311+ <request name="set_position">
3312+ <description summary="reposition the sub-surface">
3313+ This sets the position of the sub-surface, relative to the parent
3314+ surface.
3315+
3316+ The sub-surface will be moved so that its origin (top left
3317+ corner pixel) will be at the location x, y of the parent surface
3318+ coordinate system. The coordinates are not restricted to the parent
3319+ surface area. Negative values are allowed.
3320+
3321+ The initial position is 0, 0.
3322+
3323+ Position is double-buffered state on the parent surface, see
3324+ wl_subsurface and wl_surface.commit for more information.
3325+ </description>
3326+ <arg name="x" type="int" summary="x coordinate in the parent surface"/>
3327+ <arg name="y" type="int" summary="y coordinate in the parent surface"/>
3328+ </request>
3329+
3330+ <request name="place_above">
3331+ <description summary="restack the sub-surface">
3332+ This sub-surface is taken from the stack, and put back just
3333+ above the reference surface, changing the z-order of the sub-surfaces.
3334+ The reference surface must be one of the sibling surfaces, or the
3335+ parent surface. Using any other surface, including this sub-surface,
3336+ will cause a protocol error.
3337+
3338+ A new sub-surface is initially added as the top-most in the stack
3339+ of its siblings and parent.
3340+
3341+ Z-order is double-buffered state on the parent surface, see
3342+ wl_subsurface and wl_surface.commit for more information.
3343+ </description>
3344+ <arg name="sibling" type="object" interface="wl_surface"
3345+ summary="the reference surface"/>
3346+ </request>
3347+
3348+ <request name="place_below">
3349+ <description summary="restack the sub-surface">
3350+ The sub-surface is placed just below the reference surface.
3351+
3352+ See wl_subsurface.place_above.
3353+ </description>
3354+ <arg name="sibling" type="object" interface="wl_surface"
3355+ summary="the reference surface"/>
3356+ </request>
3357+
3358+ <request name="set_sync">
3359+ <description summary="set sub-surface to synchronized mode">
3360+ Change the commit behaviour of the sub-surface to synchronized
3361+ mode.
3362+
3363+ See wl_subsurface and wl_surface.commit for more information.
3364+ </description>
3365+ </request>
3366+
3367+ <request name="set_desync">
3368+ <description summary="set sub-surface to desynchronized mode">
3369+ Change the commit behaviour of the sub-surface to desynchronized
3370+ mode.
3371+
3372+ See wl_subsurface and wl_surface.commit for more information.
3373+ </description>
3374+ </request>
3375+ </interface>
3376+
3377+ <interface name="wl_fixes" version="2">
3378+ <description summary="wayland protocol fixes">
3379+ This global fixes problems with other core-protocol interfaces that
3380+ cannot be fixed in these interfaces themselves.
3381+ </description>
3382+
3383+ <enum name="error">
3384+ <description summary="wl_fixes error values">
3385+ These errors can be emitted in response to wl_fixes requests.
3386+ </description>
3387+ <entry name="invalid_ack_remove" value="0"
3388+ summary="unknown global or the global is not removed"/>
3389+ </enum>
3390+
3391+ <request name="destroy" type="destructor">
3392+ <description summary="destroys this object"/>
3393+ </request>
3394+
3395+ <request name="destroy_registry">
3396+ <description summary="destroy a wl_registry">
3397+ This request destroys a wl_registry object.
3398+
3399+ The client should no longer use the wl_registry after making this
3400+ request.
3401+
3402+ The compositor will emit a wl_display.delete_id event with the object ID
3403+ of the registry and will no longer emit any events on the registry. The
3404+ client should re-use the object ID once it receives the
3405+ wl_display.delete_id event.
3406+ </description>
3407+ <arg name="registry" type="object" interface="wl_registry"
3408+ summary="the registry to destroy"/>
3409+ </request>
3410+
3411+ <request name="ack_global_remove" since="2">
3412+ <description summary="acknowledge global removal">
3413+ Acknowledge the removal of the specified global.
3414+
3415+ If no global with the specified name exists or the global is not removed,
3416+ the wl_fixes.invalid_ack_remove protocol error will be posted.
3417+
3418+ Due to the Wayland protocol being asynchronous, the wl_global objects
3419+ cannot be destroyed immediately. For example, if a wl_global is removed
3420+ and a client attempts to bind that global around same time, it can
3421+ result in a protocol error due to an unknown global name in the bind
3422+ request.
3423+
3424+ In order to avoid crashing clients, the compositor should remove the
3425+ wl_global once it is guaranteed that no more bind requests will come.
3426+
3427+ The wl_fixes.ack_global_remove() request is used to signal to the
3428+ compositor that the client will not bind the given global anymore. After
3429+ all clients acknowledge the removal of the global, the compositor can
3430+ safely destroy it.
3431+
3432+ The client must call the wl_fixes.ack_global_remove() request in
3433+ response to a wl_registry.global_remove() event even if it did not bind
3434+ the corresponding global.
3435+ </description>
3436+ <arg name="registry" type="object" interface="wl_registry"
3437+ summary="the registry object"/>
3438+ <arg name="name" type="uint" summary="unique name of the global"/>
3439+ </request>
3440+ </interface>
3441+
3442+</protocol>
M scm_linux.go +77 -49
@@ -6,9 +6,13 @@ package wlturbo
66 import (
77 "errors"
88 "fmt"
9- "golang.org/x/sys/unix"
9+ "io"
10+ "net"
1011 "sync"
1112 "syscall"
13+ "unsafe"
14+
15+ "golang.org/x/sys/unix"
1216 )
1317
1418 // maxRecvFDs bounds how many ancillary file descriptors a single read accepts.
@@ -16,10 +20,12 @@ import (
1620 // instead of silently dropping descriptors.
1721 const maxRecvFDs = 64
1822
23+type controlBuffer struct{ data []byte }
24+
1925 // controlBufferPool provides control-message buffers sized for maxRecvFDs.
2026 var controlBufferPool = sync.Pool{
2127 New: func() interface{} {
22- return make([]byte, unix.CmsgSpace(maxRecvFDs*4))
28+ return &controlBuffer{data: make([]byte, unix.CmsgSpace(maxRecvFDs*4))}
2329 },
2430 }
2531
@@ -40,8 +46,9 @@ func (d *Display) readChunk() (int, error) {
4046 return n, err
4147 }
4248
43- oob := controlBufferPool.Get().([]byte)
44- defer controlBufferPool.Put(oob)
49+ buffer := controlBufferPool.Get().(*controlBuffer)
50+ defer controlBufferPool.Put(buffer)
51+ oob := buffer.data
4552
4653 n, oobn, flags, _, err := d.unix.ReadMsgUnix(d.recvBuf, oob)
4754 if n < 0 {
@@ -51,12 +58,12 @@ func (d *Display) readChunk() (int, error) {
5158 if oobn > 0 {
5259 fds, perr := parseFDs(oob[:oobn])
5360 d.queueFDs(fds)
54- if perr != nil && err == nil {
55- err = perr
61+ if perr != nil {
62+ err = &ProtocolError{Kind: "malformed_control", Err: perr}
5663 }
5764 }
5865
59- if flags&unix.MSG_CTRUNC != 0 && err == nil {
66+ if flags&unix.MSG_CTRUNC != 0 {
6067 // Descriptors were dropped by the kernel, so the connection can no
6168 // longer be demarshalled correctly: treat it as a protocol failure so
6269 // the caller closes the descriptors that did arrive.
@@ -68,55 +75,54 @@ func (d *Display) readChunk() (int, error) {
6875 // parseFDs extracts the file descriptors carried by socket control messages.
6976 // Descriptors parsed before an error are returned so the caller can close them.
7077 func parseFDs(oob []byte) ([]int, error) {
71- scms, err := syscall.ParseSocketControlMessage(oob)
72- if err != nil {
73- return nil, fmt.Errorf("parse control message: %w", err)
74- }
75-
7678 var fds []int
77- for i := range scms {
78- scm := &scms[i]
79- if scm.Header.Type != syscall.SCM_RIGHTS {
80- continue
79+ for len(oob) > 0 {
80+ if len(oob) < unix.SizeofCmsghdr {
81+ return fds, fmt.Errorf("short control header")
8182 }
82- parsed, err := syscall.ParseUnixRights(scm)
83- if err != nil {
84- return fds, fmt.Errorf("parse unix rights: %w", err)
83+ var hdr unix.Cmsghdr
84+ // Parse each header independently: a malformed later header must not
85+ // discard rights already decoded from earlier messages.
86+ if err := readControlHeader(oob, &hdr); err != nil {
87+ return fds, err
88+ }
89+ length := int(hdr.Len)
90+ if length < unix.SizeofCmsghdr || length > len(oob) {
91+ return fds, fmt.Errorf("invalid control length %d", length)
92+ }
93+ if hdr.Level == unix.SOL_SOCKET && hdr.Type == unix.SCM_RIGHTS {
94+ message := syscall.SocketControlMessage{Header: syscall.Cmsghdr{Len: uint64(length), Level: int32(hdr.Level), Type: int32(hdr.Type)}, Data: oob[unix.SizeofCmsghdr:length]}
95+ parsed, err := syscall.ParseUnixRights(&message)
96+ if err != nil {
97+ return fds, fmt.Errorf("parse unix rights: %w", err)
98+ }
99+ fds = append(fds, parsed...)
85100 }
86- fds = append(fds, parsed...)
101+ step := unix.CmsgSpace(length - unix.SizeofCmsghdr)
102+ if step > len(oob) {
103+ break
104+ } // final control message may omit trailing padding
105+ oob = oob[step:]
87106 }
88107 return fds, nil
89108 }
90109
91-// queueFDs appends descriptors to this connection's pending list.
92-func (d *Display) queueFDs(fds []int) {
93- if len(fds) == 0 {
94- return
110+func readControlHeader(data []byte, hdr *unix.Cmsghdr) error {
111+ // Linux cmsghdr has a native-endian size_t followed by two int32s.
112+ if len(data) < unix.SizeofCmsghdr {
113+ return fmt.Errorf("short control header")
95114 }
96- d.fdMu.Lock()
97- d.pendingFDs = append(d.pendingFDs, fds...)
98- d.fdMu.Unlock()
115+ *hdr = *(*unix.Cmsghdr)(unsafe.Pointer(&data[0]))
116+ return nil
99117 }
100118
101-// nextFD removes and returns the oldest pending descriptor.
102-func (d *Display) nextFD() (int, bool) {
103- d.fdMu.Lock()
104- defer d.fdMu.Unlock()
105- if len(d.pendingFDs) == 0 {
106- return -1, false
107- }
108- fd := d.pendingFDs[0]
109- d.pendingFDs = d.pendingFDs[1:]
110- return fd, true
111-}
119+// queueFDs is called only by the active Dispatch reader.
120+func (d *Display) queueFDs(fds []int) { d.pendingFDs = append(d.pendingFDs, fds...) }
112121
113-// closePendingFDs closes every descriptor this connection still holds.
122+// closePendingFDs is called under recvMu (including during shutdown).
114123 func (d *Display) closePendingFDs() {
115- d.fdMu.Lock()
116124 fds := d.pendingFDs
117125 d.pendingFDs = nil
118- d.fdMu.Unlock()
119-
120126 for _, fd := range fds {
121127 if fd >= 0 {
122128 _ = unix.Close(fd)
@@ -124,20 +130,42 @@ func (d *Display) closePendingFDs() {
124130 }
125131 }
126132
133+// CloseSentFD releases a descriptor after a complete successful Wayland send.
134+func CloseSentFD(fd int) error { return unix.Close(fd) }
135+
127136 // sendmsgWithFDs sends a message, attaching file descriptors when present.
128-func (d *Display) sendmsgWithFDs(buf []byte, fds []int) error {
137+// A non-nil guard runs under the send lock, so its checks and the write are
138+// ordered atomically with every other request on the connection.
139+func (d *Display) sendmsgWithFDs(buf []byte, fds []int, guard func() error) error {
129140 d.sendMu.Lock()
130141 defer d.sendMu.Unlock()
131142
132- if len(fds) == 0 {
133- _, err := d.conn.Write(buf)
134- return err
143+ if d.closed.Load() {
144+ return net.ErrClosed
135145 }
136- if d.unix == nil {
146+ if len(fds) > 0 && d.unix == nil {
137147 return errors.New("wlturbo: cannot send file descriptors over a non-Unix connection")
138148 }
139-
140- _, _, err := d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil)
149+ if guard != nil {
150+ if err := guard(); err != nil {
151+ return err
152+ }
153+ }
154+ var n int
155+ var err error
156+ if len(fds) == 0 {
157+ n, err = d.conn.Write(buf)
158+ } else {
159+ n, _, err = d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil)
160+ }
161+ if err == nil && n != len(buf) {
162+ err = io.ErrShortWrite
163+ }
164+ if err != nil {
165+ // A failed or partial write desynchronizes the stream: shut the socket
166+ // down so every later send and receive fails instead of corrupting it.
167+ _ = d.conn.Close()
168+ }
141169 return err
142170 }
143171
M shm.go +0 -1
@@ -7,7 +7,6 @@ import (
77
88 // ShmPool represents a shared memory pool
99 type ShmPool struct {
10- id uint32
1110 fd int
1211 size int
1312 data []byte
A signature.go +50 -0
@@ -0,0 +1,50 @@
1+package wlturbo
2+
3+import (
4+ "encoding/binary"
5+ "fmt"
6+)
7+
8+// validateEventBody rejects truncated, unterminated and trailing wire values
9+// before generated code decodes or registers any children.
10+func validateEventBody(sig string, body []byte) error {
11+ pos := 0
12+ for len(sig) > 0 {
13+ end := 0
14+ for end < len(sig) && sig[end] != ',' {
15+ end++
16+ }
17+ kind := sig[:end]
18+ if end == len(sig) {
19+ return fmt.Errorf("invalid signature %q", sig)
20+ }
21+ sig = sig[end+1:]
22+ if kind == "fd" {
23+ continue
24+ }
25+ if len(body)-pos < 4 {
26+ return ErrMalformedFrame
27+ }
28+ switch kind {
29+ case "int", "uint", "fixed", "object", "new_id":
30+ pos += 4
31+ case "string", "array":
32+ n := uint64(binary.LittleEndian.Uint32(body[pos:]))
33+ pos += 4
34+ padded := (n + 3) &^ uint64(3)
35+ if padded > uint64(len(body)-pos) {
36+ return ErrMalformedFrame
37+ }
38+ if kind == "string" && n > 0 && body[pos+int(n)-1] != 0 {
39+ return ErrMalformedFrame
40+ }
41+ pos += int(padded)
42+ default:
43+ return fmt.Errorf("invalid argument kind %q: %w", kind, ErrMalformedFrame)
44+ }
45+ }
46+ if pos != len(body) {
47+ return ErrMalformedFrame
48+ }
49+ return nil
50+}
M transport_test.go +46 -5
@@ -92,6 +92,13 @@ func newChunkDisplay(chunks [][]byte) (*Display, *[]EventRecord) {
9292 BaseProxy: BaseProxy{id: 7, context: d.context},
9393 records: &records,
9494 })
95+ for _, opcode := range []uint16{3, 4, 5} {
96+ signature := "uint,"
97+ if opcode == 3 {
98+ signature = "uint,uint,"
99+ }
100+ d.RegisterEventSignature(7, opcode, signature)
101+ }
95102 return d, &records
96103 }
97104
@@ -132,7 +139,7 @@ func TestDisplayDispatch_Fragmented(t *testing.T) {
132139 }
133140
134141 func TestDisplayDispatch_Coalesced(t *testing.T) {
135- first := message(7, 3, []byte{0x11, 0x12, 0x13, 0x14})
142+ first := message(7, 4, []byte{0x11, 0x12, 0x13, 0x14})
136143 second := message(7, 4, []byte{0x21, 0x22, 0x23, 0x24})
137144
138145 d, records := newChunkDisplay([][]byte{append(append([]byte{}, first...), second...)})
@@ -146,7 +153,7 @@ func TestDisplayDispatch_Coalesced(t *testing.T) {
146153 }
147154
148155 want := []EventRecord{
149- {Object: 7, Opcode: 3, Body: []byte{0x11, 0x12, 0x13, 0x14}},
156+ {Object: 7, Opcode: 4, Body: []byte{0x11, 0x12, 0x13, 0x14}},
150157 {Object: 7, Opcode: 4, Body: []byte{0x21, 0x22, 0x23, 0x24}},
151158 }
152159 if !reflect.DeepEqual(*records, want) {
@@ -157,7 +164,7 @@ func TestDisplayDispatch_Coalesced(t *testing.T) {
157164 func TestDisplayDispatch_HalfCoalesced(t *testing.T) {
158165 // A read boundary that ends between the header and the body of the second
159166 // message must not be mistaken for a frame boundary.
160- whole := append(message(7, 3, []byte{0x01, 0x02, 0x03, 0x04}),
167+ whole := append(message(7, 4, []byte{0x01, 0x02, 0x03, 0x04}),
161168 message(7, 5, []byte{0x05, 0x06, 0x07, 0x08})...)
162169
163170 split := HeaderSize + 4 + 4 // mid-body of the second message
@@ -171,7 +178,7 @@ func TestDisplayDispatch_HalfCoalesced(t *testing.T) {
171178 }
172179
173180 want := []EventRecord{
174- {Object: 7, Opcode: 3, Body: []byte{0x01, 0x02, 0x03, 0x04}},
181+ {Object: 7, Opcode: 4, Body: []byte{0x01, 0x02, 0x03, 0x04}},
175182 {Object: 7, Opcode: 5, Body: []byte{0x05, 0x06, 0x07, 0x08}},
176183 }
177184 if !reflect.DeepEqual(*records, want) {
@@ -274,6 +281,10 @@ func TestDisplayDispatch_DisplayError(t *testing.T) {
274281
275282 func TestDisplayDispatch_DeleteID(t *testing.T) {
276283 d, records := newChunkDisplay([][]byte{message(displayObjectID, 1, []byte{7, 0, 0, 0})})
284+ for d.nextID <= 7 {
285+ d.allocateID()
286+ }
287+ d.objects.Delete(uint32(7))
277288
278289 if err := d.Dispatch(); err != nil {
279290 t.Fatalf("Dispatch: %v", err)
@@ -299,7 +310,7 @@ func TestDisplayDispatch_PeerClose(t *testing.T) {
299310 },
300311 {
301312 name: "closed after a complete message",
302- chunks: [][]byte{message(7, 3, []byte{0x01, 0x02, 0x03, 0x04})},
313+ chunks: [][]byte{message(7, 4, []byte{0x01, 0x02, 0x03, 0x04})},
303314 },
304315 }
305316
@@ -350,3 +361,33 @@ func TestReadFrame(t *testing.T) {
350361 t.Fatalf("readFrame(truncated) error = %v, want %v", err, io.ErrUnexpectedEOF)
351362 }
352363 }
364+
365+func TestDeleteIDReuseAndInvalid(t *testing.T) {
366+ d := newDisplay(&chunkConn{})
367+ id := d.AllocateID()
368+ body := make([]byte, 4)
369+ binary.LittleEndian.PutUint32(body, id)
370+ if err := d.handleDisplayEvent(1, body); err != nil {
371+ t.Fatal(err)
372+ }
373+ if err := d.handleDisplayEvent(1, body); err == nil {
374+ t.Fatal("duplicate delete_id accepted")
375+ }
376+ d.nextID = 0xff000000 // simulate exhaustion
377+ if got := d.AllocateID(); got != id {
378+ t.Fatalf("reused %d, want %d", got, id)
379+ }
380+ if got := d.AllocateID(); got != 0 {
381+ t.Fatalf("allocated server-range ID %x", got)
382+ }
383+ d.objects.Store(id, d)
384+ if err := d.handleDisplayEvent(1, body); err == nil {
385+ t.Fatal("live ID deleted")
386+ }
387+ for _, bad := range []uint32{0, 1, 0xff000001} {
388+ binary.LittleEndian.PutUint32(body, bad)
389+ if err := d.handleDisplayEvent(1, body); err == nil {
390+ t.Fatalf("accepted reserved ID %x", bad)
391+ }
392+ }
393+}
M wl/wl.go +2 -18
@@ -21,14 +21,7 @@ type (
2121 RegistryGlobalRemoveHandler = wlturbo.RegistryGlobalRemoveHandler
2222 RegistryGlobalEvent = wlturbo.RegistryGlobalEvent
2323 RegistryGlobalRemoveEvent = wlturbo.RegistryGlobalRemoveEvent
24- Seat = wlturbo.Seat
25- Surface = wlturbo.Surface
26- Pointer = wlturbo.Pointer
27- Keyboard = wlturbo.Keyboard
28- Touch = wlturbo.Touch
29- Output = wlturbo.Output
30- Region = wlturbo.Region
31- Compositor = wlturbo.Compositor
24+ OwnedFD = wlturbo.OwnedFD
3225
3326 // Transport failures. Without these, a caller that imports this shim has to
3427 // import the root package as well just to classify an error.
@@ -40,10 +33,8 @@ type (
4033 var (
4134 Connect = wlturbo.Connect
4235 NewFixed = wlturbo.NewFixed
43- NewSeat = wlturbo.NewSeat
4436 NewContext = wlturbo.NewContext
45- NewSurface = wlturbo.NewSurface
46- NewCompositor = wlturbo.NewCompositor
37+ CloseSentFD = wlturbo.CloseSentFD
4738 CreateAnonymousFile = wlturbo.CreateAnonymousFile
4839 MapMemory = wlturbo.MapMemory
4940 UnmapMemory = wlturbo.UnmapMemory
@@ -57,10 +48,3 @@ var (
5748 ErrUnknownOpcode = wlturbo.ErrUnknownOpcode
5849 ErrDisplayError = wlturbo.ErrDisplayError
5950 )
60-
61-// Seat capability constants
62-const (
63- SeatCapabilityPointer = wlturbo.SeatCapabilityPointer
64- SeatCapabilityKeyboard = wlturbo.SeatCapabilityKeyboard
65- SeatCapabilityTouch = wlturbo.SeatCapabilityTouch
66-)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close