feat(core): generate canonical core bindings and harden event transport
- commit
- 75766210f33cf0f15489c80f4b602dfd3331388f
- author
- brice · · 2026-09-28 07:15 UTC
- parent
- d5c3b4c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
13 files · +4780 -687
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() } | |
| @@ -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 +175 -224
| @@ -13,6 +13,7 @@ import ( | |||
| 13 | 13 | "net" | |
| 14 | 14 | "os" | |
| 15 | 15 | "path/filepath" | |
| 16 | + | "strings" | |
| 16 | 17 | "sync" | |
| 17 | 18 | "sync/atomic" | |
| 18 | 19 | ) | |
| @@ -44,13 +45,15 @@ type Object interface { | |||
| 44 | 45 | ||
| 45 | 46 | // Display represents a connection to the Wayland display | |
| 46 | 47 | 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 | + | sendMu sync.Mutex | |
| 53 | + | recvMu sync.Mutex | |
| 54 | + | signatures sync.Map | |
| 55 | + | listenerMu sync.Mutex | |
| 56 | + | listeners sync.Map // map[uint32]map[uint16][]func([]byte) | |
| 54 | 57 | ||
| 55 | 58 | // High-performance event dispatcher | |
| 56 | 59 | dispatcher *EventDispatcher | |
| @@ -73,7 +76,6 @@ type Display struct { | |||
| 73 | 76 | readErr error | |
| 74 | 77 | maxMsgLen uint32 | |
| 75 | 78 | closed atomic.Bool | |
| 76 | - | fdMu sync.Mutex | |
| 77 | 79 | pendingFDs []int | |
| 78 | 80 | } | |
| 79 | 81 | ||
| @@ -103,11 +105,12 @@ func newDisplay(conn net.Conn) *Display { | |||
| 103 | 105 | ||
| 104 | 106 | // Registry represents the global registry | |
| 105 | 107 | type Registry struct { | |
| 106 | - | id uint32 | |
| 107 | - | display *Display | |
| 108 | - | globals map[uint32]Global | |
| 109 | - | mu sync.RWMutex | |
| 110 | - | handlers map[string]GlobalHandler | |
| 108 | + | id uint32 | |
| 109 | + | display *Display | |
| 110 | + | globals map[uint32]Global | |
| 111 | + | mu sync.RWMutex | |
| 112 | + | handlers map[string]GlobalHandler | |
| 113 | + | removeHandlers []RegistryGlobalRemoveHandler | |
| 111 | 114 | } | |
| 112 | 115 | ||
| 113 | 116 | // callbackObject represents a wl_callback object | |
| @@ -119,6 +122,12 @@ type callbackObject struct { | |||
| 119 | 122 | func (c *callbackObject) ID() uint32 { | |
| 120 | 123 | return c.id | |
| 121 | 124 | } | |
| 125 | + | func (c *callbackObject) EventSignature(op uint16) (string, bool) { | |
| 126 | + | if op == 0 { | |
| 127 | + | return "uint,", true | |
| 128 | + | } | |
| 129 | + | return "", false | |
| 130 | + | } | |
| 122 | 131 | ||
| 123 | 132 | // Dispatch handles callback events (opcode 0 = done) | |
| 124 | 133 | func (c *callbackObject) Dispatch(event *Event) { | |
| @@ -126,6 +135,7 @@ func (c *callbackObject) Dispatch(event *Event) { | |||
| 126 | 135 | return | |
| 127 | 136 | } | |
| 128 | 137 | c.display.notifyListeners(c.id, 0, event.data) | |
| 138 | + | c.display.context.Unregister(c) | |
| 129 | 139 | } | |
| 130 | 140 | ||
| 131 | 141 | // Global represents a global object | |
| @@ -138,6 +148,16 @@ type Global struct { | |||
| 138 | 148 | // GlobalHandler is called when a global is announced | |
| 139 | 149 | type GlobalHandler func(registry *Registry, name uint32, version uint32) | |
| 140 | 150 | ||
| 151 | + | // ConnectFromConn attaches to an established Wayland socket and requests its registry. | |
| 152 | + | func ConnectFromConn(conn net.Conn) (*Display, error) { | |
| 153 | + | d := newDisplay(conn) | |
| 154 | + | if err := d.getRegistry(); err != nil { | |
| 155 | + | _ = d.Close() | |
| 156 | + | return nil, fmt.Errorf("get registry: %w", err) | |
| 157 | + | } | |
| 158 | + | return d, nil | |
| 159 | + | } | |
| 160 | + | ||
| 141 | 161 | // Connect connects to the Wayland display and requests the registry. | |
| 142 | 162 | func Connect(socketPath string) (*Display, error) { | |
| 143 | 163 | if socketPath == "" { | |
| @@ -161,16 +181,7 @@ func Connect(socketPath string) (*Display, error) { | |||
| 161 | 181 | return nil, fmt.Errorf("failed to connect to Wayland: %w", err) | |
| 162 | 182 | } | |
| 163 | 183 | ||
| 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 | |
| 184 | + | return ConnectFromConn(conn) | |
| 174 | 185 | } | |
| 175 | 186 | ||
| 176 | 187 | // Close closes the display connection. It is safe to call more than once; | |
| @@ -179,8 +190,14 @@ func (d *Display) Close() error { | |||
| 179 | 190 | if !d.closed.CompareAndSwap(false, true) { | |
| 180 | 191 | return nil | |
| 181 | 192 | } | |
| 193 | + | d.context.closed.Store(true) | |
| 194 | + | err := d.conn.Close() | |
| 195 | + | d.sendMu.Lock() | |
| 196 | + | d.sendMu.Unlock() | |
| 197 | + | d.recvMu.Lock() | |
| 182 | 198 | d.closePendingFDs() | |
| 183 | - | return d.conn.Close() | |
| 199 | + | d.recvMu.Unlock() | |
| 200 | + | return err | |
| 184 | 201 | } | |
| 185 | 202 | ||
| 186 | 203 | // Closed reports whether the connection has been closed. | |
| @@ -235,8 +252,8 @@ func (d *Display) SendRequestWithFDs(objectID uint32, opcode uint16, fds []int, | |||
| 235 | 252 | ||
| 236 | 253 | // Update header with actual size in bytes | |
| 237 | 254 | bufLen := buf.Len() | |
| 238 | - | if bufLen > 0xFFFF { | |
| 239 | - | return fmt.Errorf("message too large: %d bytes", bufLen) | |
| 255 | + | if bufLen > 0xFFFF || bufLen%4 != 0 { | |
| 256 | + | return fmt.Errorf("invalid message size: %d bytes", bufLen) | |
| 240 | 257 | } | |
| 241 | 258 | size := uint32(bufLen) // Safe: checked above | |
| 242 | 259 | binary.LittleEndian.PutUint32(header[0:4], objectID) | |
| @@ -263,7 +280,7 @@ func (d *Display) marshalArg(buf *bytes.Buffer, arg interface{}) error { | |||
| 263 | 280 | case string: | |
| 264 | 281 | // String format: length (including null) + string + null + padding | |
| 265 | 282 | strlen := len(v) + 1 | |
| 266 | - | if strlen > 0xFFFFFFFF { | |
| 283 | + | if strlen > 0xffff-HeaderSize { | |
| 267 | 284 | return fmt.Errorf("string too long: %d bytes", strlen) | |
| 268 | 285 | } | |
| 269 | 286 | if err := binary.Write(buf, binary.LittleEndian, uint32(strlen)); err != nil { // Safe: checked above | |
| @@ -279,7 +296,7 @@ func (d *Display) marshalArg(buf *bytes.Buffer, arg interface{}) error { | |||
| 279 | 296 | case []byte: | |
| 280 | 297 | // Array format: length + data + padding | |
| 281 | 298 | arrlen := len(v) | |
| 282 | - | if arrlen > 0xFFFFFFFF { | |
| 299 | + | if arrlen > 0xffff-HeaderSize { | |
| 283 | 300 | return fmt.Errorf("array too long: %d bytes", arrlen) | |
| 284 | 301 | } | |
| 285 | 302 | if err := binary.Write(buf, binary.LittleEndian, uint32(arrlen)); err != nil { | |
| @@ -329,6 +346,9 @@ func (d *Display) Dispatch() error { | |||
| 329 | 346 | } | |
| 330 | 347 | ||
| 331 | 348 | frame, err := d.nextFrame() | |
| 349 | + | if d.closed.Load() { | |
| 350 | + | return net.ErrClosed | |
| 351 | + | } | |
| 332 | 352 | if err != nil { | |
| 333 | 353 | return d.fail(err) | |
| 334 | 354 | } | |
| @@ -346,6 +366,9 @@ func (d *Display) fail(err error) error { | |||
| 346 | 366 | if errors.As(err, &perr) { | |
| 347 | 367 | d.closePendingFDs() | |
| 348 | 368 | } | |
| 369 | + | if errors.Is(err, io.ErrUnexpectedEOF) { | |
| 370 | + | d.closePendingFDs() | |
| 371 | + | } | |
| 349 | 372 | return err | |
| 350 | 373 | } | |
| 351 | 374 | ||
| @@ -376,6 +399,9 @@ func (d *Display) nextFrame() (receivedFrame, error) { | |||
| 376 | 399 | } | |
| 377 | 400 | } | |
| 378 | 401 | if err := d.fill(); err != nil { | |
| 402 | + | if d.closed.Load() { | |
| 403 | + | return receivedFrame{}, net.ErrClosed | |
| 404 | + | } | |
| 379 | 405 | return receivedFrame{}, err | |
| 380 | 406 | } | |
| 381 | 407 | } | |
| @@ -416,40 +442,85 @@ func (d *Display) fill() error { | |||
| 416 | 442 | return nil | |
| 417 | 443 | } | |
| 418 | 444 | ||
| 419 | - | // dispatchFrame delivers one complete frame to the object that owns it. | |
| 445 | + | // signatureProvider is implemented by generated objects; every opcode must | |
| 446 | + | // have a known signature before any descriptor can be assigned or dispatched. | |
| 447 | + | type signatureProvider interface{ EventSignature(uint16) (string, bool) } | |
| 448 | + | ||
| 449 | + | // dispatchFrame delivers one complete frame with precisely its signature's FDs. | |
| 420 | 450 | func (d *Display) dispatchFrame(f receivedFrame) error { | |
| 421 | 451 | if f.object == displayObjectID { | |
| 452 | + | if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 { | |
| 453 | + | return &ProtocolError{Kind: "extra_fd", Err: ErrMalformedFrame} | |
| 454 | + | } | |
| 422 | 455 | return d.handleDisplayEvent(f.opcode, f.body) | |
| 423 | 456 | } | |
| 424 | - | ||
| 425 | 457 | obj, ok := d.objects.Load(f.object) | |
| 426 | 458 | 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, | |
| 459 | + | return &ProtocolError{Kind: "unknown_object", Object: f.object, Opcode: f.opcode, Err: ErrUnknownObject} | |
| 460 | + | } | |
| 461 | + | sig, valid := "", false | |
| 462 | + | p, isGenerated := obj.(signatureProvider) | |
| 463 | + | if isGenerated { | |
| 464 | + | sig, valid = p.EventSignature(f.opcode) | |
| 465 | + | } | |
| 466 | + | // Legacy custom proxies may declare their signatures with RegisterEventSignature. | |
| 467 | + | if !valid && !isGenerated { | |
| 468 | + | if v, ok := d.signatures.Load(signatureKey{f.object, f.opcode}); ok { | |
| 469 | + | sig, valid = v.(string), true | |
| 433 | 470 | } | |
| 434 | 471 | } | |
| 472 | + | if !valid { | |
| 473 | + | return &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode} | |
| 474 | + | } | |
| 475 | + | if err := validateEventBody(sig, f.body); err != nil { | |
| 476 | + | return &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err} | |
| 477 | + | } | |
| 478 | + | count := strings.Count(sig, "fd,") | |
| 479 | + | if len(d.pendingFDs) < count { | |
| 480 | + | return &ProtocolError{Kind: "missing_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |
| 481 | + | } | |
| 482 | + | ev := eventPool.Get().(*Event) | |
| 483 | + | *ev = Event{ProxyID: f.object, Opcode: f.opcode, data: f.body, display: d} | |
| 484 | + | if count != 0 { | |
| 485 | + | ev.fds = make([]*OwnedFD, count) | |
| 486 | + | for i := range ev.fds { | |
| 487 | + | ev.fds[i] = &OwnedFD{fd: d.pendingFDs[i]} | |
| 488 | + | } | |
| 489 | + | d.pendingFDs = d.pendingFDs[count:] | |
| 490 | + | } | |
| 491 | + | defer func() { | |
| 492 | + | for _, fd := range ev.fds { | |
| 493 | + | _ = fd.Close() | |
| 494 | + | } | |
| 495 | + | *ev = Event{} | |
| 496 | + | eventPool.Put(ev) | |
| 497 | + | }() | |
| 498 | + | // FDs from a single recvmsg can accompany multiple coalesced frames. Once | |
| 499 | + | // all buffered bytes have been framed, surplus descriptors are not legal. | |
| 500 | + | if len(d.rbuf) == 0 && len(d.pendingFDs) != 0 { | |
| 501 | + | return &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |
| 502 | + | } | |
| 503 | + | if proxy, ok := obj.(Proxy); ok { | |
| 504 | + | proxy.Dispatch(ev) | |
| 505 | + | } else { | |
| 506 | + | d.dispatcher.Dispatch(f.object, f.opcode, f.body) | |
| 507 | + | d.notifyListeners(f.object, f.opcode, f.body) | |
| 508 | + | } | |
| 509 | + | return nil | |
| 510 | + | } | |
| 435 | 511 | ||
| 436 | - | // Server-created objects are announced by an event on a known object. | |
| 437 | - | d.handleServerObject(f.object, f.opcode, f.body) | |
| 512 | + | type signatureKey struct { | |
| 513 | + | object uint32 | |
| 514 | + | opcode uint16 | |
| 515 | + | } | |
| 438 | 516 | ||
| 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 | |
| 517 | + | // RegisterEventSignature defines a signature for a legacy proxy. Generated | |
| 518 | + | // proxies supply it themselves and cannot be overridden. | |
| 519 | + | func (d *Display) RegisterEventSignature(object uint32, opcode uint16, signature string) { | |
| 520 | + | if object == 0 || object == displayObjectID { | |
| 521 | + | return | |
| 447 | 522 | } | |
| 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 | |
| 523 | + | d.signatures.Store(signatureKey{object, opcode}, signature) | |
| 453 | 524 | } | |
| 454 | 525 | ||
| 455 | 526 | // notifyListeners runs the listeners registered for an object and opcode. | |
| @@ -466,11 +537,7 @@ func (d *Display) notifyListeners(objectID uint32, opcode uint16, body []byte) { | |||
| 466 | 537 | if !ok { | |
| 467 | 538 | return | |
| 468 | 539 | } | |
| 469 | - | handlerSlice, ok := handlers.(*[]func([]byte)) | |
| 470 | - | if !ok || handlerSlice == nil { | |
| 471 | - | return | |
| 472 | - | } | |
| 473 | - | for _, handler := range *handlerSlice { | |
| 540 | + | for _, handler := range handlers.([]func([]byte)) { | |
| 474 | 541 | if handler != nil { | |
| 475 | 542 | handler(body) | |
| 476 | 543 | } | |
| @@ -481,8 +548,8 @@ func (d *Display) notifyListeners(objectID uint32, opcode uint16, body []byte) { | |||
| 481 | 548 | func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | |
| 482 | 549 | switch opcode { | |
| 483 | 550 | 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} | |
| 551 | + | if err := validateEventBody("object,uint,string,", data); err != nil { | |
| 552 | + | return &ProtocolError{Kind: "malformed_display_error", Err: err} | |
| 486 | 553 | } | |
| 487 | 554 | event := &Event{ProxyID: displayObjectID, Opcode: opcode, data: data, display: d} | |
| 488 | 555 | objectID := event.Uint32() | |
| @@ -495,11 +562,12 @@ func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | |||
| 495 | 562 | return d.lastError | |
| 496 | 563 | ||
| 497 | 564 | case 1: // delete_id | |
| 498 | - | if len(data) < 4 { | |
| 565 | + | if len(data) != 4 { | |
| 499 | 566 | return &ProtocolError{Kind: "short_delete_id", Object: displayObjectID, Opcode: opcode, Size: uint32(len(data)) + HeaderSize, Err: ErrMalformedFrame} | |
| 500 | 567 | } | |
| 501 | 568 | id := binary.LittleEndian.Uint32(data[0:4]) | |
| 502 | 569 | d.objects.Delete(id) | |
| 570 | + | d.context.proxies.Delete(id) | |
| 503 | 571 | return nil | |
| 504 | 572 | ||
| 505 | 573 | default: | |
| @@ -507,55 +575,42 @@ func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | |||
| 507 | 575 | } | |
| 508 | 576 | } | |
| 509 | 577 | ||
| 510 | - | // Roundtrip performs a synchronous roundtrip to the compositor | |
| 578 | + | // Roundtrip waits for one wl_display.sync callback. Concurrent event loops | |
| 579 | + | // must not call Roundtrip while another goroutine dispatches this connection. | |
| 511 | 580 | 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 { | |
| 581 | + | callback, err := d.Sync() | |
| 582 | + | if err != nil { | |
| 522 | 583 | return err | |
| 523 | 584 | } | |
| 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 { | |
| 585 | + | for { | |
| 586 | + | if err = d.Dispatch(); err != nil { | |
| 535 | 587 | return err | |
| 536 | 588 | } | |
| 537 | - | select { | |
| 538 | - | case err := <-done: | |
| 539 | - | return err | |
| 540 | - | default: | |
| 589 | + | if _, exists := d.objects.Load(callback.ID()); !exists { | |
| 590 | + | return nil | |
| 541 | 591 | } | |
| 542 | 592 | } | |
| 543 | - | ||
| 544 | - | return fmt.Errorf("roundtrip failed: max iterations reached") | |
| 545 | 593 | } | |
| 546 | 594 | ||
| 547 | 595 | // AddListener adds an event listener for an object | |
| 548 | 596 | func (d *Display) AddListener(objectID uint32, opcode uint16, handler func([]byte)) { | |
| 597 | + | if handler == nil { | |
| 598 | + | return | |
| 599 | + | } | |
| 549 | 600 | // Load or create listener map for object | |
| 550 | 601 | listeners, _ := d.listeners.LoadOrStore(objectID, &sync.Map{}) | |
| 551 | 602 | opcodeMap := listeners.(*sync.Map) | |
| 552 | 603 | ||
| 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) | |
| 604 | + | d.listenerMu.Lock() | |
| 605 | + | defer d.listenerMu.Unlock() | |
| 606 | + | var handlers []func([]byte) | |
| 607 | + | if old, ok := opcodeMap.Load(opcode); ok { | |
| 608 | + | handlers = old.([]func([]byte)) | |
| 609 | + | } | |
| 610 | + | next := make([]func([]byte), len(handlers)+1) | |
| 611 | + | copy(next, handlers) | |
| 612 | + | next[len(handlers)] = handler | |
| 613 | + | opcodeMap.Store(opcode, next) | |
| 559 | 614 | } | |
| 560 | 615 | ||
| 561 | 616 | // getRegistry gets the global registry | |
| @@ -581,15 +636,13 @@ func (r *Registry) ID() uint32 { | |||
| 581 | 636 | // handleGlobal handles global announcements | |
| 582 | 637 | func (r *Registry) handleGlobal(data []byte) { | |
| 583 | 638 | if len(data) < 8 { | |
| 584 | - | // log.Printf("wlclient: handleGlobal: data too short (%d bytes)", len(data)) | |
| 585 | 639 | return | |
| 586 | 640 | } | |
| 587 | 641 | ||
| 588 | 642 | name := binary.LittleEndian.Uint32(data[0:4]) | |
| 589 | 643 | ifaceLen := binary.LittleEndian.Uint32(data[4:8]) | |
| 590 | 644 | ||
| 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)) | |
| 645 | + | if ifaceLen == 0 || uint64(ifaceLen)+12 > uint64(len(data)) || data[8+ifaceLen-1] != 0 { | |
| 593 | 646 | return | |
| 594 | 647 | } | |
| 595 | 648 | ||
| @@ -600,15 +653,12 @@ func (r *Registry) handleGlobal(data []byte) { | |||
| 600 | 653 | padding := (4 - (ifaceLen % 4)) % 4 | |
| 601 | 654 | versionOffset := 8 + int(ifaceLen) + int(padding) | |
| 602 | 655 | ||
| 603 | - | if len(data) < versionOffset+4 { | |
| 604 | - | // log.Printf("wlclient: handleGlobal: insufficient data for version (need %d, have %d)", versionOffset+4, len(data)) | |
| 656 | + | if len(data) != versionOffset+4 { | |
| 605 | 657 | return | |
| 606 | 658 | } | |
| 607 | 659 | ||
| 608 | 660 | version := binary.LittleEndian.Uint32(data[versionOffset:]) | |
| 609 | 661 | ||
| 610 | - | // log.Printf("wlclient: global announced: %s v%d (name=%d)", iface, version, name) | |
| 611 | - | ||
| 612 | 662 | // Store global | |
| 613 | 663 | r.mu.Lock() | |
| 614 | 664 | r.globals[name] = Global{ | |
| @@ -619,15 +669,19 @@ func (r *Registry) handleGlobal(data []byte) { | |||
| 619 | 669 | r.mu.Unlock() | |
| 620 | 670 | ||
| 621 | 671 | // Call specific handler if registered | |
| 622 | - | if handler, ok := r.handlers[iface]; ok { | |
| 623 | - | // log.Printf("wlclient: calling handler for interface %s", iface) | |
| 672 | + | r.mu.RLock() | |
| 673 | + | handler, specific := r.handlers[iface] | |
| 674 | + | wildcard, all := r.handlers["*"] | |
| 675 | + | r.mu.RUnlock() | |
| 676 | + | if specific { | |
| 677 | + | ||
| 624 | 678 | handler(r, name, version) | |
| 625 | 679 | } | |
| 626 | 680 | ||
| 627 | 681 | // 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) | |
| 682 | + | if all { | |
| 683 | + | ||
| 684 | + | wildcard(r, name, version) | |
| 631 | 685 | } | |
| 632 | 686 | } | |
| 633 | 687 | ||
| @@ -641,11 +695,17 @@ func (r *Registry) handleGlobalRemove(data []byte) { | |||
| 641 | 695 | ||
| 642 | 696 | r.mu.Lock() | |
| 643 | 697 | delete(r.globals, name) | |
| 698 | + | handlers := append([]RegistryGlobalRemoveHandler(nil), r.removeHandlers...) | |
| 644 | 699 | r.mu.Unlock() | |
| 700 | + | for _, handler := range handlers { | |
| 701 | + | handler.HandleRegistryGlobalRemove(RegistryGlobalRemoveEvent{Registry: r, Name: name}) | |
| 702 | + | } | |
| 645 | 703 | } | |
| 646 | 704 | ||
| 647 | 705 | // AddHandler adds a handler for a specific interface | |
| 648 | 706 | func (r *Registry) AddHandler(iface string, handler GlobalHandler) { | |
| 707 | + | r.mu.Lock() | |
| 708 | + | defer r.mu.Unlock() | |
| 649 | 709 | r.handlers[iface] = handler | |
| 650 | 710 | } | |
| 651 | 711 | ||
| @@ -656,7 +716,7 @@ func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy) | |||
| 656 | 716 | proxy.SetID(r.display.allocateID()) | |
| 657 | 717 | } | |
| 658 | 718 | ||
| 659 | - | // Ensure proxy has a context set - CRITICAL FIX | |
| 719 | + | // Bootstrap a generated proxy with this display's context. | |
| 660 | 720 | if proxy.Context() == nil { | |
| 661 | 721 | if baseProxy, ok := proxy.(*BaseProxy); ok { | |
| 662 | 722 | baseProxy.SetContext(r.display.Context()) | |
| @@ -667,12 +727,18 @@ func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy) | |||
| 667 | 727 | } | |
| 668 | 728 | } | |
| 669 | 729 | ||
| 730 | + | if proxy.Context() != r.display.Context() { | |
| 731 | + | return fmt.Errorf("proxy belongs to another display") | |
| 732 | + | } | |
| 733 | + | if proxy.ID() == displayObjectID || proxy.ID() == r.id { | |
| 734 | + | return fmt.Errorf("reserved object ID") | |
| 735 | + | } | |
| 736 | + | if old, exists := r.display.objects.Load(proxy.ID()); exists && old != proxy { | |
| 737 | + | return fmt.Errorf("object ID %d already registered", proxy.ID()) | |
| 738 | + | } | |
| 670 | 739 | // Register the proxy | |
| 671 | 740 | proxy.Context().Register(proxy) | |
| 672 | 741 | ||
| 673 | - | // Also register directly in display objects map | |
| 674 | - | r.display.objects.Store(proxy.ID(), proxy) | |
| 675 | - | ||
| 676 | 742 | // Send bind request (opcode 0) with proper arguments | |
| 677 | 743 | if err := r.display.SendRequest(r.id, 0, name, iface, version, proxy.ID()); err != nil { | |
| 678 | 744 | proxy.Context().Unregister(proxy) | |
| @@ -718,118 +784,3 @@ func (r *Registry) FindGlobal(iface string) (Global, bool) { | |||
| 718 | 784 | } | |
| 719 | 785 | return Global{}, false | |
| 720 | 786 | } | |
| 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 | - | } | |
| @@ -13,6 +13,7 @@ import ( | |||||
| 13 | "net" | 13 | "net" | ||
| 14 | "os" | 14 | "os" | ||
| 15 | "path/filepath" | 15 | "path/filepath" | ||
| 16 | + | "strings" | |||
| 16 | "sync" | 17 | "sync" | ||
| 17 | "sync/atomic" | 18 | "sync/atomic" | ||
| 18 | ) | 19 | ) | ||
| @@ -44,13 +45,15 @@ type Object interface { | |||||
| 44 | 45 | ||||
| 45 | // Display represents a connection to the Wayland display | 46 | // Display represents a connection to the Wayland display | ||
| 46 | type Display struct { | 47 | type Display struct { | ||
| 47 | - | conn net.Conn | 48 | + | conn net.Conn |
| 48 | - | unix *net.UnixConn // set when conn is a Unix socket, nil otherwise | 49 | + | unix *net.UnixConn // set when conn is a Unix socket, nil otherwise |
| 49 | - | objects sync.Map // map[uint32]Object | 50 | + | objects sync.Map // map[uint32]Object |
| 50 | - | nextID uint32 | 51 | + | nextID uint32 |
| 51 | - | sendMu sync.Mutex | 52 | + | sendMu sync.Mutex |
| 52 | - | recvMu sync.Mutex | 53 | + | recvMu sync.Mutex |
| 53 | - | listeners sync.Map // map[uint32]map[uint16][]func([]byte) | 54 | + | signatures sync.Map |
| 55 | + | listenerMu sync.Mutex | |||
| 56 | + | listeners sync.Map // map[uint32]map[uint16][]func([]byte) | |||
| 54 | 57 | ||||
| 55 | // High-performance event dispatcher | 58 | // High-performance event dispatcher | ||
| 56 | dispatcher *EventDispatcher | 59 | dispatcher *EventDispatcher | ||
| @@ -73,7 +76,6 @@ type Display struct { | |||||
| 73 | readErr error | 76 | readErr error | ||
| 74 | maxMsgLen uint32 | 77 | maxMsgLen uint32 | ||
| 75 | closed atomic.Bool | 78 | closed atomic.Bool | ||
| 76 | - | fdMu sync.Mutex | |||
| 77 | pendingFDs []int | 79 | pendingFDs []int | ||
| 78 | } | 80 | } | ||
| 79 | 81 | ||||
| @@ -103,11 +105,12 @@ func newDisplay(conn net.Conn) *Display { | |||||
| 103 | 105 | ||||
| 104 | // Registry represents the global registry | 106 | // Registry represents the global registry | ||
| 105 | type Registry struct { | 107 | type Registry struct { | ||
| 106 | - | id uint32 | 108 | + | id uint32 |
| 107 | - | display *Display | 109 | + | display *Display |
| 108 | - | globals map[uint32]Global | 110 | + | globals map[uint32]Global |
| 109 | - | mu sync.RWMutex | 111 | + | mu sync.RWMutex |
| 110 | - | handlers map[string]GlobalHandler | 112 | + | handlers map[string]GlobalHandler |
| 113 | + | removeHandlers []RegistryGlobalRemoveHandler | |||
| 111 | } | 114 | } | ||
| 112 | 115 | ||||
| 113 | // callbackObject represents a wl_callback object | 116 | // callbackObject represents a wl_callback object | ||
| @@ -119,6 +122,12 @@ type callbackObject struct { | |||||
| 119 | func (c *callbackObject) ID() uint32 { | 122 | func (c *callbackObject) ID() uint32 { | ||
| 120 | return c.id | 123 | return c.id | ||
| 121 | } | 124 | } | ||
| 125 | + | func (c *callbackObject) EventSignature(op uint16) (string, bool) { | |||
| 126 | + | if op == 0 { | |||
| 127 | + | return "uint,", true | |||
| 128 | + | } | |||
| 129 | + | return "", false | |||
| 130 | + | } | |||
| 122 | 131 | ||||
| 123 | // Dispatch handles callback events (opcode 0 = done) | 132 | // Dispatch handles callback events (opcode 0 = done) | ||
| 124 | func (c *callbackObject) Dispatch(event *Event) { | 133 | func (c *callbackObject) Dispatch(event *Event) { | ||
| @@ -126,6 +135,7 @@ func (c *callbackObject) Dispatch(event *Event) { | |||||
| 126 | return | 135 | return | ||
| 127 | } | 136 | } | ||
| 128 | c.display.notifyListeners(c.id, 0, event.data) | 137 | c.display.notifyListeners(c.id, 0, event.data) | ||
| 138 | + | c.display.context.Unregister(c) | |||
| 129 | } | 139 | } | ||
| 130 | 140 | ||||
| 131 | // Global represents a global object | 141 | // Global represents a global object | ||
| @@ -138,6 +148,16 @@ type Global struct { | |||||
| 138 | // GlobalHandler is called when a global is announced | 148 | // GlobalHandler is called when a global is announced | ||
| 139 | type GlobalHandler func(registry *Registry, name uint32, version uint32) | 149 | type GlobalHandler func(registry *Registry, name uint32, version uint32) | ||
| 140 | 150 | ||||
| 151 | + | // ConnectFromConn attaches to an established Wayland socket and requests its registry. | |||
| 152 | + | func ConnectFromConn(conn net.Conn) (*Display, error) { | |||
| 153 | + | d := newDisplay(conn) | |||
| 154 | + | if err := d.getRegistry(); err != nil { | |||
| 155 | + | _ = d.Close() | |||
| 156 | + | return nil, fmt.Errorf("get registry: %w", err) | |||
| 157 | + | } | |||
| 158 | + | return d, nil | |||
| 159 | + | } | |||
| 160 | + | ||||
| 141 | // Connect connects to the Wayland display and requests the registry. | 161 | // Connect connects to the Wayland display and requests the registry. | ||
| 142 | func Connect(socketPath string) (*Display, error) { | 162 | func Connect(socketPath string) (*Display, error) { | ||
| 143 | if socketPath == "" { | 163 | if socketPath == "" { | ||
| @@ -161,16 +181,7 @@ func Connect(socketPath string) (*Display, error) { | |||||
| 161 | return nil, fmt.Errorf("failed to connect to Wayland: %w", err) | 181 | return nil, fmt.Errorf("failed to connect to Wayland: %w", err) | ||
| 162 | } | 182 | } | ||
| 163 | 183 | ||||
| 164 | - | d := newDisplay(conn) | 184 | + | return ConnectFromConn(conn) |
| 165 | - | ||||
| 166 | - | // Get registry | |||
| 167 | - | if err := d.getRegistry(); err != nil { | |||
| 168 | - | _ = d.Close() | |||
| 169 | - | return nil, fmt.Errorf("failed to get registry: %w", err) | |||
| 170 | - | } | |||
| 171 | - | ||||
| 172 | - | // Don't do initial roundtrip here - let the caller do it after setting up handlers | |||
| 173 | - | return d, nil | |||
| 174 | } | 185 | } | ||
| 175 | 186 | ||||
| 176 | // Close closes the display connection. It is safe to call more than once; | 187 | // Close closes the display connection. It is safe to call more than once; | ||
| @@ -179,8 +190,14 @@ func (d *Display) Close() error { | |||||
| 179 | if !d.closed.CompareAndSwap(false, true) { | 190 | if !d.closed.CompareAndSwap(false, true) { | ||
| 180 | return nil | 191 | return nil | ||
| 181 | } | 192 | } | ||
| 193 | + | d.context.closed.Store(true) | |||
| 194 | + | err := d.conn.Close() | |||
| 195 | + | d.sendMu.Lock() | |||
| 196 | + | d.sendMu.Unlock() | |||
| 197 | + | d.recvMu.Lock() | |||
| 182 | d.closePendingFDs() | 198 | d.closePendingFDs() | ||
| 183 | - | return d.conn.Close() | 199 | + | d.recvMu.Unlock() |
| 200 | + | return err | |||
| 184 | } | 201 | } | ||
| 185 | 202 | ||||
| 186 | // Closed reports whether the connection has been closed. | 203 | // Closed reports whether the connection has been closed. | ||
| @@ -235,8 +252,8 @@ func (d *Display) SendRequestWithFDs(objectID uint32, opcode uint16, fds []int, | |||||
| 235 | 252 | ||||
| 236 | // Update header with actual size in bytes | 253 | // Update header with actual size in bytes | ||
| 237 | bufLen := buf.Len() | 254 | bufLen := buf.Len() | ||
| 238 | - | if bufLen > 0xFFFF { | 255 | + | if bufLen > 0xFFFF || bufLen%4 != 0 { |
| 239 | - | return fmt.Errorf("message too large: %d bytes", bufLen) | 256 | + | return fmt.Errorf("invalid message size: %d bytes", bufLen) |
| 240 | } | 257 | } | ||
| 241 | size := uint32(bufLen) // Safe: checked above | 258 | size := uint32(bufLen) // Safe: checked above | ||
| 242 | binary.LittleEndian.PutUint32(header[0:4], objectID) | 259 | binary.LittleEndian.PutUint32(header[0:4], objectID) | ||
| @@ -263,7 +280,7 @@ func (d *Display) marshalArg(buf *bytes.Buffer, arg interface{}) error { | |||||
| 263 | case string: | 280 | case string: | ||
| 264 | // String format: length (including null) + string + null + padding | 281 | // String format: length (including null) + string + null + padding | ||
| 265 | strlen := len(v) + 1 | 282 | strlen := len(v) + 1 | ||
| 266 | - | if strlen > 0xFFFFFFFF { | 283 | + | if strlen > 0xffff-HeaderSize { |
| 267 | return fmt.Errorf("string too long: %d bytes", strlen) | 284 | return fmt.Errorf("string too long: %d bytes", strlen) | ||
| 268 | } | 285 | } | ||
| 269 | if err := binary.Write(buf, binary.LittleEndian, uint32(strlen)); err != nil { // Safe: checked above | 286 | if err := binary.Write(buf, binary.LittleEndian, uint32(strlen)); err != nil { // Safe: checked above | ||
| @@ -279,7 +296,7 @@ func (d *Display) marshalArg(buf *bytes.Buffer, arg interface{}) error { | |||||
| 279 | case []byte: | 296 | case []byte: | ||
| 280 | // Array format: length + data + padding | 297 | // Array format: length + data + padding | ||
| 281 | arrlen := len(v) | 298 | arrlen := len(v) | ||
| 282 | - | if arrlen > 0xFFFFFFFF { | 299 | + | if arrlen > 0xffff-HeaderSize { |
| 283 | return fmt.Errorf("array too long: %d bytes", arrlen) | 300 | return fmt.Errorf("array too long: %d bytes", arrlen) | ||
| 284 | } | 301 | } | ||
| 285 | if err := binary.Write(buf, binary.LittleEndian, uint32(arrlen)); err != nil { | 302 | if err := binary.Write(buf, binary.LittleEndian, uint32(arrlen)); err != nil { | ||
| @@ -329,6 +346,9 @@ func (d *Display) Dispatch() error { | |||||
| 329 | } | 346 | } | ||
| 330 | 347 | ||||
| 331 | frame, err := d.nextFrame() | 348 | frame, err := d.nextFrame() | ||
| 349 | + | if d.closed.Load() { | |||
| 350 | + | return net.ErrClosed | |||
| 351 | + | } | |||
| 332 | if err != nil { | 352 | if err != nil { | ||
| 333 | return d.fail(err) | 353 | return d.fail(err) | ||
| 334 | } | 354 | } | ||
| @@ -346,6 +366,9 @@ func (d *Display) fail(err error) error { | |||||
| 346 | if errors.As(err, &perr) { | 366 | if errors.As(err, &perr) { | ||
| 347 | d.closePendingFDs() | 367 | d.closePendingFDs() | ||
| 348 | } | 368 | } | ||
| 369 | + | if errors.Is(err, io.ErrUnexpectedEOF) { | |||
| 370 | + | d.closePendingFDs() | |||
| 371 | + | } | |||
| 349 | return err | 372 | return err | ||
| 350 | } | 373 | } | ||
| 351 | 374 | ||||
| @@ -376,6 +399,9 @@ func (d *Display) nextFrame() (receivedFrame, error) { | |||||
| 376 | } | 399 | } | ||
| 377 | } | 400 | } | ||
| 378 | if err := d.fill(); err != nil { | 401 | if err := d.fill(); err != nil { | ||
| 402 | + | if d.closed.Load() { | |||
| 403 | + | return receivedFrame{}, net.ErrClosed | |||
| 404 | + | } | |||
| 379 | return receivedFrame{}, err | 405 | return receivedFrame{}, err | ||
| 380 | } | 406 | } | ||
| 381 | } | 407 | } | ||
| @@ -416,40 +442,85 @@ func (d *Display) fill() error { | |||||
| 416 | return nil | 442 | return nil | ||
| 417 | } | 443 | } | ||
| 418 | 444 | ||||
| 419 | - | // dispatchFrame delivers one complete frame to the object that owns it. | 445 | + | // signatureProvider is implemented by generated objects; every opcode must |
| 446 | + | // have a known signature before any descriptor can be assigned or dispatched. | |||
| 447 | + | type signatureProvider interface{ EventSignature(uint16) (string, bool) } | |||
| 448 | + | ||||
| 449 | + | // dispatchFrame delivers one complete frame with precisely its signature's FDs. | |||
| 420 | func (d *Display) dispatchFrame(f receivedFrame) error { | 450 | func (d *Display) dispatchFrame(f receivedFrame) error { | ||
| 421 | if f.object == displayObjectID { | 451 | if f.object == displayObjectID { | ||
| 452 | + | if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 { | |||
| 453 | + | return &ProtocolError{Kind: "extra_fd", Err: ErrMalformedFrame} | |||
| 454 | + | } | |||
| 422 | return d.handleDisplayEvent(f.opcode, f.body) | 455 | return d.handleDisplayEvent(f.opcode, f.body) | ||
| 423 | } | 456 | } | ||
| 424 | - | ||||
| 425 | obj, ok := d.objects.Load(f.object) | 457 | obj, ok := d.objects.Load(f.object) | ||
| 426 | if !ok { | 458 | if !ok { | ||
| 427 | - | return &ProtocolError{ | 459 | + | return &ProtocolError{Kind: "unknown_object", Object: f.object, Opcode: f.opcode, Err: ErrUnknownObject} |
| 428 | - | Kind: "unknown_object", | 460 | + | } |
| 429 | - | Object: f.object, | 461 | + | sig, valid := "", false |
| 430 | - | Opcode: f.opcode, | 462 | + | p, isGenerated := obj.(signatureProvider) |
| 431 | - | Size: uint32(len(f.body)) + HeaderSize, | 463 | + | if isGenerated { |
| 432 | - | Err: ErrUnknownObject, | 464 | + | sig, valid = p.EventSignature(f.opcode) |
| 465 | + | } | |||
| 466 | + | // Legacy custom proxies may declare their signatures with RegisterEventSignature. | |||
| 467 | + | if !valid && !isGenerated { | |||
| 468 | + | if v, ok := d.signatures.Load(signatureKey{f.object, f.opcode}); ok { | |||
| 469 | + | sig, valid = v.(string), true | |||
| 433 | } | 470 | } | ||
| 434 | } | 471 | } | ||
| 472 | + | if !valid { | |||
| 473 | + | return &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode} | |||
| 474 | + | } | |||
| 475 | + | if err := validateEventBody(sig, f.body); err != nil { | |||
| 476 | + | return &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err} | |||
| 477 | + | } | |||
| 478 | + | count := strings.Count(sig, "fd,") | |||
| 479 | + | if len(d.pendingFDs) < count { | |||
| 480 | + | return &ProtocolError{Kind: "missing_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |||
| 481 | + | } | |||
| 482 | + | ev := eventPool.Get().(*Event) | |||
| 483 | + | *ev = Event{ProxyID: f.object, Opcode: f.opcode, data: f.body, display: d} | |||
| 484 | + | if count != 0 { | |||
| 485 | + | ev.fds = make([]*OwnedFD, count) | |||
| 486 | + | for i := range ev.fds { | |||
| 487 | + | ev.fds[i] = &OwnedFD{fd: d.pendingFDs[i]} | |||
| 488 | + | } | |||
| 489 | + | d.pendingFDs = d.pendingFDs[count:] | |||
| 490 | + | } | |||
| 491 | + | defer func() { | |||
| 492 | + | for _, fd := range ev.fds { | |||
| 493 | + | _ = fd.Close() | |||
| 494 | + | } | |||
| 495 | + | *ev = Event{} | |||
| 496 | + | eventPool.Put(ev) | |||
| 497 | + | }() | |||
| 498 | + | // FDs from a single recvmsg can accompany multiple coalesced frames. Once | |||
| 499 | + | // all buffered bytes have been framed, surplus descriptors are not legal. | |||
| 500 | + | if len(d.rbuf) == 0 && len(d.pendingFDs) != 0 { | |||
| 501 | + | return &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |||
| 502 | + | } | |||
| 503 | + | if proxy, ok := obj.(Proxy); ok { | |||
| 504 | + | proxy.Dispatch(ev) | |||
| 505 | + | } else { | |||
| 506 | + | d.dispatcher.Dispatch(f.object, f.opcode, f.body) | |||
| 507 | + | d.notifyListeners(f.object, f.opcode, f.body) | |||
| 508 | + | } | |||
| 509 | + | return nil | |||
| 510 | + | } | |||
| 435 | 511 | ||||
| 436 | - | // Server-created objects are announced by an event on a known object. | 512 | + | type signatureKey struct { |
| 437 | - | d.handleServerObject(f.object, f.opcode, f.body) | 513 | + | object uint32 |
| 514 | + | opcode uint16 | |||
| 515 | + | } | |||
| 438 | 516 | ||||
| 439 | - | if proxy, ok := obj.(Proxy); ok && proxy != nil { | 517 | + | // RegisterEventSignature defines a signature for a legacy proxy. Generated |
| 440 | - | proxy.Dispatch(&Event{ | 518 | + | // proxies supply it themselves and cannot be overridden. |
| 441 | - | ProxyID: f.object, | 519 | + | func (d *Display) RegisterEventSignature(object uint32, opcode uint16, signature string) { |
| 442 | - | Opcode: f.opcode, | 520 | + | if object == 0 || object == displayObjectID { |
| 443 | - | data: f.body, | 521 | + | return |
| 444 | - | display: d, | |||
| 445 | - | }) | |||
| 446 | - | return nil | |||
| 447 | } | 522 | } | ||
| 448 | - | 523 | + | d.signatures.Store(signatureKey{object, opcode}, signature) | |
| 449 | - | // High-performance dispatcher, then compatibility listeners. | |||
| 450 | - | d.dispatcher.Dispatch(f.object, f.opcode, f.body) | |||
| 451 | - | d.notifyListeners(f.object, f.opcode, f.body) | |||
| 452 | - | return nil | |||
| 453 | } | 524 | } | ||
| 454 | 525 | ||||
| 455 | // notifyListeners runs the listeners registered for an object and opcode. | 526 | // notifyListeners runs the listeners registered for an object and opcode. | ||
| @@ -466,11 +537,7 @@ func (d *Display) notifyListeners(objectID uint32, opcode uint16, body []byte) { | |||||
| 466 | if !ok { | 537 | if !ok { | ||
| 467 | return | 538 | return | ||
| 468 | } | 539 | } | ||
| 469 | - | handlerSlice, ok := handlers.(*[]func([]byte)) | 540 | + | for _, handler := range handlers.([]func([]byte)) { |
| 470 | - | if !ok || handlerSlice == nil { | |||
| 471 | - | return | |||
| 472 | - | } | |||
| 473 | - | for _, handler := range *handlerSlice { | |||
| 474 | if handler != nil { | 541 | if handler != nil { | ||
| 475 | handler(body) | 542 | handler(body) | ||
| 476 | } | 543 | } | ||
| @@ -481,8 +548,8 @@ func (d *Display) notifyListeners(objectID uint32, opcode uint16, body []byte) { | |||||
| 481 | func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | 548 | func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | ||
| 482 | switch opcode { | 549 | switch opcode { | ||
| 483 | case 0: // error | 550 | case 0: // error | ||
| 484 | - | if len(data) < 8 { | 551 | + | if err := validateEventBody("object,uint,string,", data); err != nil { |
| 485 | - | return &ProtocolError{Kind: "short_display_error", Object: displayObjectID, Opcode: opcode, Size: uint32(len(data)) + HeaderSize, Err: ErrMalformedFrame} | 552 | + | return &ProtocolError{Kind: "malformed_display_error", Err: err} |
| 486 | } | 553 | } | ||
| 487 | event := &Event{ProxyID: displayObjectID, Opcode: opcode, data: data, display: d} | 554 | event := &Event{ProxyID: displayObjectID, Opcode: opcode, data: data, display: d} | ||
| 488 | objectID := event.Uint32() | 555 | objectID := event.Uint32() | ||
| @@ -495,11 +562,12 @@ func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | |||||
| 495 | return d.lastError | 562 | return d.lastError | ||
| 496 | 563 | ||||
| 497 | case 1: // delete_id | 564 | case 1: // delete_id | ||
| 498 | - | if len(data) < 4 { | 565 | + | if len(data) != 4 { |
| 499 | return &ProtocolError{Kind: "short_delete_id", Object: displayObjectID, Opcode: opcode, Size: uint32(len(data)) + HeaderSize, Err: ErrMalformedFrame} | 566 | return &ProtocolError{Kind: "short_delete_id", Object: displayObjectID, Opcode: opcode, Size: uint32(len(data)) + HeaderSize, Err: ErrMalformedFrame} | ||
| 500 | } | 567 | } | ||
| 501 | id := binary.LittleEndian.Uint32(data[0:4]) | 568 | id := binary.LittleEndian.Uint32(data[0:4]) | ||
| 502 | d.objects.Delete(id) | 569 | d.objects.Delete(id) | ||
| 570 | + | d.context.proxies.Delete(id) | |||
| 503 | return nil | 571 | return nil | ||
| 504 | 572 | ||||
| 505 | default: | 573 | default: | ||
| @@ -507,55 +575,42 @@ func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | |||||
| 507 | } | 575 | } | ||
| 508 | } | 576 | } | ||
| 509 | 577 | ||||
| 510 | - | // Roundtrip performs a synchronous roundtrip to the compositor | 578 | + | // Roundtrip waits for one wl_display.sync callback. Concurrent event loops |
| 579 | + | // must not call Roundtrip while another goroutine dispatches this connection. | |||
| 511 | func (d *Display) Roundtrip() error { | 580 | func (d *Display) Roundtrip() error { | ||
| 512 | - | callbackID := d.allocateID() | 581 | + | callback, err := d.Sync() |
| 513 | - | done := make(chan error, 1) | 582 | + | if err != nil { |
| 514 | - | ||||
| 515 | - | d.AddListener(callbackID, 0, func(_ []byte) { | |||
| 516 | - | d.objects.Delete(callbackID) | |||
| 517 | - | done <- nil | |||
| 518 | - | }) | |||
| 519 | - | ||||
| 520 | - | // Send sync request (opcode 0) | |||
| 521 | - | if err := d.SendRequest(displayObjectID, 0, callbackID); err != nil { | |||
| 522 | return err | 583 | return err | ||
| 523 | } | 584 | } | ||
| 524 | - | 585 | + | for { | |
| 525 | - | d.objects.Store(callbackID, &callbackObject{ | 586 | + | if err = d.Dispatch(); err != nil { |
| 526 | - | BaseProxy: BaseProxy{ | |||
| 527 | - | context: d.Context(), | |||
| 528 | - | id: callbackID, | |||
| 529 | - | }, | |||
| 530 | - | display: d, | |||
| 531 | - | }) | |||
| 532 | - | ||||
| 533 | - | for i := 0; i < 1000; i++ { | |||
| 534 | - | if err := d.Dispatch(); err != nil { | |||
| 535 | return err | 587 | return err | ||
| 536 | } | 588 | } | ||
| 537 | - | select { | 589 | + | if _, exists := d.objects.Load(callback.ID()); !exists { |
| 538 | - | case err := <-done: | 590 | + | return nil |
| 539 | - | return err | |||
| 540 | - | default: | |||
| 541 | } | 591 | } | ||
| 542 | } | 592 | } | ||
| 543 | - | ||||
| 544 | - | return fmt.Errorf("roundtrip failed: max iterations reached") | |||
| 545 | } | 593 | } | ||
| 546 | 594 | ||||
| 547 | // AddListener adds an event listener for an object | 595 | // AddListener adds an event listener for an object | ||
| 548 | func (d *Display) AddListener(objectID uint32, opcode uint16, handler func([]byte)) { | 596 | func (d *Display) AddListener(objectID uint32, opcode uint16, handler func([]byte)) { | ||
| 597 | + | if handler == nil { | |||
| 598 | + | return | |||
| 599 | + | } | |||
| 549 | // Load or create listener map for object | 600 | // Load or create listener map for object | ||
| 550 | listeners, _ := d.listeners.LoadOrStore(objectID, &sync.Map{}) | 601 | listeners, _ := d.listeners.LoadOrStore(objectID, &sync.Map{}) | ||
| 551 | opcodeMap := listeners.(*sync.Map) | 602 | opcodeMap := listeners.(*sync.Map) | ||
| 552 | 603 | ||||
| 553 | - | // Load or create handlers slice for opcode | 604 | + | d.listenerMu.Lock() |
| 554 | - | handlers, _ := opcodeMap.LoadOrStore(opcode, &[]func([]byte){}) | 605 | + | defer d.listenerMu.Unlock() |
| 555 | - | handlerSlice := handlers.(*[]func([]byte)) | 606 | + | var handlers []func([]byte) |
| 556 | - | 607 | + | if old, ok := opcodeMap.Load(opcode); ok { | |
| 557 | - | // Add handler (thread-safe) | 608 | + | handlers = old.([]func([]byte)) |
| 558 | - | *handlerSlice = append(*handlerSlice, handler) | 609 | + | } |
| 610 | + | next := make([]func([]byte), len(handlers)+1) | |||
| 611 | + | copy(next, handlers) | |||
| 612 | + | next[len(handlers)] = handler | |||
| 613 | + | opcodeMap.Store(opcode, next) | |||
| 559 | } | 614 | } | ||
| 560 | 615 | ||||
| 561 | // getRegistry gets the global registry | 616 | // getRegistry gets the global registry | ||
| @@ -581,15 +636,13 @@ func (r *Registry) ID() uint32 { | |||||
| 581 | // handleGlobal handles global announcements | 636 | // handleGlobal handles global announcements | ||
| 582 | func (r *Registry) handleGlobal(data []byte) { | 637 | func (r *Registry) handleGlobal(data []byte) { | ||
| 583 | if len(data) < 8 { | 638 | if len(data) < 8 { | ||
| 584 | - | // log.Printf("wlclient: handleGlobal: data too short (%d bytes)", len(data)) | |||
| 585 | return | 639 | return | ||
| 586 | } | 640 | } | ||
| 587 | 641 | ||||
| 588 | name := binary.LittleEndian.Uint32(data[0:4]) | 642 | name := binary.LittleEndian.Uint32(data[0:4]) | ||
| 589 | ifaceLen := binary.LittleEndian.Uint32(data[4:8]) | 643 | ifaceLen := binary.LittleEndian.Uint32(data[4:8]) | ||
| 590 | 644 | ||||
| 591 | - | if len(data) < 8+int(ifaceLen)+4 { | 645 | + | if ifaceLen == 0 || uint64(ifaceLen)+12 > uint64(len(data)) || data[8+ifaceLen-1] != 0 { |
| 592 | - | // log.Printf("wlclient: handleGlobal: insufficient data for interface string (need %d, have %d)", 8+int(ifaceLen)+4, len(data)) | |||
| 593 | return | 646 | return | ||
| 594 | } | 647 | } | ||
| 595 | 648 | ||||
| @@ -600,15 +653,12 @@ func (r *Registry) handleGlobal(data []byte) { | |||||
| 600 | padding := (4 - (ifaceLen % 4)) % 4 | 653 | padding := (4 - (ifaceLen % 4)) % 4 | ||
| 601 | versionOffset := 8 + int(ifaceLen) + int(padding) | 654 | versionOffset := 8 + int(ifaceLen) + int(padding) | ||
| 602 | 655 | ||||
| 603 | - | if len(data) < versionOffset+4 { | 656 | + | if len(data) != versionOffset+4 { |
| 604 | - | // log.Printf("wlclient: handleGlobal: insufficient data for version (need %d, have %d)", versionOffset+4, len(data)) | |||
| 605 | return | 657 | return | ||
| 606 | } | 658 | } | ||
| 607 | 659 | ||||
| 608 | version := binary.LittleEndian.Uint32(data[versionOffset:]) | 660 | version := binary.LittleEndian.Uint32(data[versionOffset:]) | ||
| 609 | 661 | ||||
| 610 | - | // log.Printf("wlclient: global announced: %s v%d (name=%d)", iface, version, name) | |||
| 611 | - | ||||
| 612 | // Store global | 662 | // Store global | ||
| 613 | r.mu.Lock() | 663 | r.mu.Lock() | ||
| 614 | r.globals[name] = Global{ | 664 | r.globals[name] = Global{ | ||
| @@ -619,15 +669,19 @@ func (r *Registry) handleGlobal(data []byte) { | |||||
| 619 | r.mu.Unlock() | 669 | r.mu.Unlock() | ||
| 620 | 670 | ||||
| 621 | // Call specific handler if registered | 671 | // Call specific handler if registered | ||
| 622 | - | if handler, ok := r.handlers[iface]; ok { | 672 | + | r.mu.RLock() |
| 623 | - | // log.Printf("wlclient: calling handler for interface %s", iface) | 673 | + | handler, specific := r.handlers[iface] |
| 674 | + | wildcard, all := r.handlers["*"] | |||
| 675 | + | r.mu.RUnlock() | |||
| 676 | + | if specific { | |||
| 677 | + | ||||
| 624 | handler(r, name, version) | 678 | handler(r, name, version) | ||
| 625 | } | 679 | } | ||
| 626 | 680 | ||||
| 627 | // Call wildcard handler if registered | 681 | // Call wildcard handler if registered | ||
| 628 | - | if handler, ok := r.handlers["*"]; ok { | 682 | + | if all { |
| 629 | - | // log.Printf("wlclient: calling wildcard handler for interface %s", iface) | 683 | + | |
| 630 | - | handler(r, name, version) | 684 | + | wildcard(r, name, version) |
| 631 | } | 685 | } | ||
| 632 | } | 686 | } | ||
| 633 | 687 | ||||
| @@ -641,11 +695,17 @@ func (r *Registry) handleGlobalRemove(data []byte) { | |||||
| 641 | 695 | ||||
| 642 | r.mu.Lock() | 696 | r.mu.Lock() | ||
| 643 | delete(r.globals, name) | 697 | delete(r.globals, name) | ||
| 698 | + | handlers := append([]RegistryGlobalRemoveHandler(nil), r.removeHandlers...) | |||
| 644 | r.mu.Unlock() | 699 | r.mu.Unlock() | ||
| 700 | + | for _, handler := range handlers { | |||
| 701 | + | handler.HandleRegistryGlobalRemove(RegistryGlobalRemoveEvent{Registry: r, Name: name}) | |||
| 702 | + | } | |||
| 645 | } | 703 | } | ||
| 646 | 704 | ||||
| 647 | // AddHandler adds a handler for a specific interface | 705 | // AddHandler adds a handler for a specific interface | ||
| 648 | func (r *Registry) AddHandler(iface string, handler GlobalHandler) { | 706 | func (r *Registry) AddHandler(iface string, handler GlobalHandler) { | ||
| 707 | + | r.mu.Lock() | |||
| 708 | + | defer r.mu.Unlock() | |||
| 649 | r.handlers[iface] = handler | 709 | r.handlers[iface] = handler | ||
| 650 | } | 710 | } | ||
| 651 | 711 | ||||
| @@ -656,7 +716,7 @@ func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy) | |||||
| 656 | proxy.SetID(r.display.allocateID()) | 716 | proxy.SetID(r.display.allocateID()) | ||
| 657 | } | 717 | } | ||
| 658 | 718 | ||||
| 659 | - | // Ensure proxy has a context set - CRITICAL FIX | 719 | + | // Bootstrap a generated proxy with this display's context. |
| 660 | if proxy.Context() == nil { | 720 | if proxy.Context() == nil { | ||
| 661 | if baseProxy, ok := proxy.(*BaseProxy); ok { | 721 | if baseProxy, ok := proxy.(*BaseProxy); ok { | ||
| 662 | baseProxy.SetContext(r.display.Context()) | 722 | baseProxy.SetContext(r.display.Context()) | ||
| @@ -667,12 +727,18 @@ func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy) | |||||
| 667 | } | 727 | } | ||
| 668 | } | 728 | } | ||
| 669 | 729 | ||||
| 730 | + | if proxy.Context() != r.display.Context() { | |||
| 731 | + | return fmt.Errorf("proxy belongs to another display") | |||
| 732 | + | } | |||
| 733 | + | if proxy.ID() == displayObjectID || proxy.ID() == r.id { | |||
| 734 | + | return fmt.Errorf("reserved object ID") | |||
| 735 | + | } | |||
| 736 | + | if old, exists := r.display.objects.Load(proxy.ID()); exists && old != proxy { | |||
| 737 | + | return fmt.Errorf("object ID %d already registered", proxy.ID()) | |||
| 738 | + | } | |||
| 670 | // Register the proxy | 739 | // Register the proxy | ||
| 671 | proxy.Context().Register(proxy) | 740 | proxy.Context().Register(proxy) | ||
| 672 | 741 | ||||
| 673 | - | // Also register directly in display objects map | |||
| 674 | - | r.display.objects.Store(proxy.ID(), proxy) | |||
| 675 | - | ||||
| 676 | // Send bind request (opcode 0) with proper arguments | 742 | // Send bind request (opcode 0) with proper arguments | ||
| 677 | if err := r.display.SendRequest(r.id, 0, name, iface, version, proxy.ID()); err != nil { | 743 | if err := r.display.SendRequest(r.id, 0, name, iface, version, proxy.ID()); err != nil { | ||
| 678 | proxy.Context().Unregister(proxy) | 744 | proxy.Context().Unregister(proxy) | ||
| @@ -718,118 +784,3 @@ func (r *Registry) FindGlobal(iface string) (Global, bool) { | |||||
| 718 | } | 784 | } | ||
| 719 | return Global{}, false | 785 | return Global{}, false | ||
| 720 | } | 786 | } | ||
| 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 | - | } | |||
M core.go +75 -372
| @@ -3,10 +3,10 @@ package wlturbo | |||
| 3 | 3 | import ( | |
| 4 | 4 | "encoding/binary" | |
| 5 | 5 | "errors" | |
| 6 | - | ||
| 7 | - | // "log" | |
| 8 | 6 | "sync" | |
| 9 | 7 | "sync/atomic" | |
| 8 | + | ||
| 9 | + | "golang.org/x/sys/unix" | |
| 10 | 10 | ) | |
| 11 | 11 | ||
| 12 | 12 | // Context provides a compatibility layer for wl.Context | |
| @@ -37,6 +37,8 @@ type Event struct { | |||
| 37 | 37 | data []byte | |
| 38 | 38 | offset int | |
| 39 | 39 | display *Display | |
| 40 | + | fds []*OwnedFD | |
| 41 | + | fdIndex int | |
| 40 | 42 | } | |
| 41 | 43 | ||
| 42 | 44 | // Data returns the raw event data | |
| @@ -74,15 +76,12 @@ func (c *Context) SendRequestWithFDs(proxy Proxy, opcode uint32, fds []int, args | |||
| 74 | 76 | ||
| 75 | 77 | // Register registers a proxy object | |
| 76 | 78 | func (c *Context) Register(proxy Proxy) { | |
| 77 | - | if proxy != nil && proxy.ID() != 0 { | |
| 79 | + | if proxy != nil && proxy.ID() != 0 && !c.closed.Load() { | |
| 80 | + | if proxy.Context() != c { | |
| 81 | + | return | |
| 82 | + | } | |
| 78 | 83 | c.proxies.Store(proxy.ID(), proxy) | |
| 79 | 84 | c.display.objects.Store(proxy.ID(), proxy) | |
| 80 | - | // Debug large IDs | |
| 81 | - | // if proxy.ID() > 1000000 { | |
| 82 | - | // log.Printf("wlclient: WARNING: Registering proxy with suspicious ID %d (0x%x)", proxy.ID(), proxy.ID()) | |
| 83 | - | // } else if proxy.ID() == 5 { | |
| 84 | - | // log.Printf("wlclient: Registered output manager proxy with ID 5") | |
| 85 | - | // } | |
| 86 | 85 | } | |
| 87 | 86 | } | |
| 88 | 87 | ||
| @@ -221,19 +220,61 @@ func (e *Event) Array() []byte { | |||
| 221 | 220 | return arr | |
| 222 | 221 | } | |
| 223 | 222 | ||
| 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. | |
| 223 | + | // OwnedFD owns one received descriptor. Close is idempotent; Take transfers | |
| 224 | + | // ownership to the caller (who must close it). Descriptor zero is valid. | |
| 225 | + | type OwnedFD struct { | |
| 226 | + | mu sync.Mutex | |
| 227 | + | fd int | |
| 228 | + | } | |
| 229 | + | ||
| 230 | + | func (f *OwnedFD) Take() (int, error) { | |
| 231 | + | if f == nil { | |
| 232 | + | return -1, errors.New("missing descriptor") | |
| 233 | + | } | |
| 234 | + | f.mu.Lock() | |
| 235 | + | defer f.mu.Unlock() | |
| 236 | + | if f.fd < 0 { | |
| 237 | + | return -1, errors.New("descriptor already taken or closed") | |
| 238 | + | } | |
| 239 | + | n := f.fd | |
| 240 | + | f.fd = -1 | |
| 241 | + | return n, nil | |
| 242 | + | } | |
| 243 | + | func (f *OwnedFD) Close() error { | |
| 244 | + | if f == nil { | |
| 245 | + | return nil | |
| 246 | + | } | |
| 247 | + | f.mu.Lock() | |
| 248 | + | defer f.mu.Unlock() | |
| 249 | + | if f.fd < 0 { | |
| 250 | + | return nil | |
| 251 | + | } | |
| 252 | + | n := f.fd | |
| 253 | + | f.fd = -1 | |
| 254 | + | return unix.Close(n) | |
| 255 | + | } | |
| 256 | + | ||
| 257 | + | // FD transfers ownership of the next descriptor assigned to this event. | |
| 258 | + | // An unclaimed descriptor is closed when dispatch completes. | |
| 259 | + | func (e *Event) FD() *OwnedFD { | |
| 260 | + | if e.fdIndex >= len(e.fds) { | |
| 261 | + | return nil | |
| 262 | + | } | |
| 263 | + | fd := e.fds[e.fdIndex] | |
| 264 | + | e.fds[e.fdIndex] = nil | |
| 265 | + | e.fdIndex++ | |
| 266 | + | return fd | |
| 267 | + | } | |
| 268 | + | ||
| 269 | + | // Fd is the legacy raw descriptor API; prefer FD for explicit ownership. | |
| 228 | 270 | func (e *Event) Fd() uintptr { | |
| 229 | - | if e.display != nil { | |
| 230 | - | if fd, ok := e.display.nextFD(); ok { | |
| 231 | - | return uintptr(fd) | |
| 232 | - | } | |
| 271 | + | fd := e.FD() | |
| 272 | + | if fd == nil { | |
| 273 | + | _ = e.Uint32() | |
| 274 | + | return 0 | |
| 233 | 275 | } | |
| 234 | - | // No descriptor was delivered: consume the placeholder argument. | |
| 235 | - | _ = e.Uint32() | |
| 236 | - | return 0 | |
| 276 | + | n, _ := fd.Take() | |
| 277 | + | return uintptr(n) | |
| 237 | 278 | } | |
| 238 | 279 | ||
| 239 | 280 | // NewId reads a new object ID from the event | |
| @@ -278,32 +319,24 @@ type RegistryGlobalRemoveEvent struct { | |||
| 278 | 319 | ||
| 279 | 320 | // AddGlobalHandler adds a global handler to the registry | |
| 280 | 321 | 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) | |
| 322 | + | if handler == nil { | |
| 323 | + | return | |
| 288 | 324 | } | |
| 289 | - | ||
| 290 | 325 | 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) | |
| 326 | + | if global, ok := r.FindGlobalByName(name); ok { | |
| 327 | + | handler.HandleRegistryGlobal(RegistryGlobalEvent{Registry: r, Name: name, Interface: global.Interface, Version: version}) | |
| 300 | 328 | } | |
| 301 | 329 | }) | |
| 302 | 330 | } | |
| 303 | 331 | ||
| 304 | - | // AddGlobalRemoveHandler adds a global remove handler (placeholder) | |
| 332 | + | // AddGlobalRemoveHandler registers a handler for registry removals. | |
| 305 | 333 | func (r *Registry) AddGlobalRemoveHandler(handler RegistryGlobalRemoveHandler) { | |
| 306 | - | // Would need to implement removal handling | |
| 334 | + | if handler == nil { | |
| 335 | + | return | |
| 336 | + | } | |
| 337 | + | r.mu.Lock() | |
| 338 | + | r.removeHandlers = append(r.removeHandlers, handler) | |
| 339 | + | r.mu.Unlock() | |
| 307 | 340 | } | |
| 308 | 341 | ||
| 309 | 342 | // FindGlobalByName finds a global by its name ID | |
| @@ -340,342 +373,12 @@ func (d *Display) Sync() (Object, error) { | |||
| 340 | 373 | display: d, | |
| 341 | 374 | } | |
| 342 | 375 | ||
| 343 | - | // Send sync request (opcode 0) | |
| 376 | + | // Store before sending: the compositor may reply immediately. | |
| 377 | + | d.context.Register(callback) | |
| 344 | 378 | if err := d.SendRequest(1, 0, callbackID); err != nil { | |
| 379 | + | d.context.Unregister(callback) | |
| 345 | 380 | return nil, err | |
| 346 | 381 | } | |
| 347 | 382 | ||
| 348 | - | // Store the callback object | |
| 349 | - | d.objects.Store(callbackID, callback) | |
| 350 | - | ||
| 351 | 383 | return callback, nil | |
| 352 | 384 | } | |
| 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) | |
| 534 | - | return nil, err | |
| 535 | - | } | |
| 536 | - | ||
| 537 | - | return callback, nil | |
| 538 | - | } | |
| 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 | - | } | |
| @@ -3,10 +3,10 @@ package wlturbo | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "encoding/binary" | 4 | "encoding/binary" | ||
| 5 | "errors" | 5 | "errors" | ||
| 6 | - | ||||
| 7 | - | // "log" | |||
| 8 | "sync" | 6 | "sync" | ||
| 9 | "sync/atomic" | 7 | "sync/atomic" | ||
| 8 | + | ||||
| 9 | + | "golang.org/x/sys/unix" | |||
| 10 | ) | 10 | ) | ||
| 11 | 11 | ||||
| 12 | // Context provides a compatibility layer for wl.Context | 12 | // Context provides a compatibility layer for wl.Context | ||
| @@ -37,6 +37,8 @@ type Event struct { | |||||
| 37 | data []byte | 37 | data []byte | ||
| 38 | offset int | 38 | offset int | ||
| 39 | display *Display | 39 | display *Display | ||
| 40 | + | fds []*OwnedFD | |||
| 41 | + | fdIndex int | |||
| 40 | } | 42 | } | ||
| 41 | 43 | ||||
| 42 | // Data returns the raw event data | 44 | // Data returns the raw event data | ||
| @@ -74,15 +76,12 @@ func (c *Context) SendRequestWithFDs(proxy Proxy, opcode uint32, fds []int, args | |||||
| 74 | 76 | ||||
| 75 | // Register registers a proxy object | 77 | // Register registers a proxy object | ||
| 76 | func (c *Context) Register(proxy Proxy) { | 78 | func (c *Context) Register(proxy Proxy) { | ||
| 77 | - | if proxy != nil && proxy.ID() != 0 { | 79 | + | if proxy != nil && proxy.ID() != 0 && !c.closed.Load() { |
| 80 | + | if proxy.Context() != c { | |||
| 81 | + | return | |||
| 82 | + | } | |||
| 78 | c.proxies.Store(proxy.ID(), proxy) | 83 | c.proxies.Store(proxy.ID(), proxy) | ||
| 79 | c.display.objects.Store(proxy.ID(), proxy) | 84 | c.display.objects.Store(proxy.ID(), proxy) | ||
| 80 | - | // Debug large IDs | |||
| 81 | - | // if proxy.ID() > 1000000 { | |||
| 82 | - | // log.Printf("wlclient: WARNING: Registering proxy with suspicious ID %d (0x%x)", proxy.ID(), proxy.ID()) | |||
| 83 | - | // } else if proxy.ID() == 5 { | |||
| 84 | - | // log.Printf("wlclient: Registered output manager proxy with ID 5") | |||
| 85 | - | // } | |||
| 86 | } | 85 | } | ||
| 87 | } | 86 | } | ||
| 88 | 87 | ||||
| @@ -221,19 +220,61 @@ func (e *Event) Array() []byte { | |||||
| 221 | return arr | 220 | return arr | ||
| 222 | } | 221 | } | ||
| 223 | 222 | ||||
| 224 | - | // Fd reads a file descriptor that was delivered out-of-band for this event. | 223 | + | // OwnedFD owns one received descriptor. Close is idempotent; Take transfers |
| 225 | - | // Descriptors are queued on the connection in arrival order, as the Wayland | 224 | + | // ownership to the caller (who must close it). Descriptor zero is valid. |
| 226 | - | // wire protocol requires. The returned descriptor is owned by the caller, | 225 | + | type OwnedFD struct { |
| 227 | - | // which must close it. It returns 0 when the event carried no descriptor. | 226 | + | mu sync.Mutex |
| 227 | + | fd int | |||
| 228 | + | } | |||
| 229 | + | ||||
| 230 | + | func (f *OwnedFD) Take() (int, error) { | |||
| 231 | + | if f == nil { | |||
| 232 | + | return -1, errors.New("missing descriptor") | |||
| 233 | + | } | |||
| 234 | + | f.mu.Lock() | |||
| 235 | + | defer f.mu.Unlock() | |||
| 236 | + | if f.fd < 0 { | |||
| 237 | + | return -1, errors.New("descriptor already taken or closed") | |||
| 238 | + | } | |||
| 239 | + | n := f.fd | |||
| 240 | + | f.fd = -1 | |||
| 241 | + | return n, nil | |||
| 242 | + | } | |||
| 243 | + | func (f *OwnedFD) Close() error { | |||
| 244 | + | if f == nil { | |||
| 245 | + | return nil | |||
| 246 | + | } | |||
| 247 | + | f.mu.Lock() | |||
| 248 | + | defer f.mu.Unlock() | |||
| 249 | + | if f.fd < 0 { | |||
| 250 | + | return nil | |||
| 251 | + | } | |||
| 252 | + | n := f.fd | |||
| 253 | + | f.fd = -1 | |||
| 254 | + | return unix.Close(n) | |||
| 255 | + | } | |||
| 256 | + | ||||
| 257 | + | // FD transfers ownership of the next descriptor assigned to this event. | |||
| 258 | + | // An unclaimed descriptor is closed when dispatch completes. | |||
| 259 | + | func (e *Event) FD() *OwnedFD { | |||
| 260 | + | if e.fdIndex >= len(e.fds) { | |||
| 261 | + | return nil | |||
| 262 | + | } | |||
| 263 | + | fd := e.fds[e.fdIndex] | |||
| 264 | + | e.fds[e.fdIndex] = nil | |||
| 265 | + | e.fdIndex++ | |||
| 266 | + | return fd | |||
| 267 | + | } | |||
| 268 | + | ||||
| 269 | + | // Fd is the legacy raw descriptor API; prefer FD for explicit ownership. | |||
| 228 | func (e *Event) Fd() uintptr { | 270 | func (e *Event) Fd() uintptr { | ||
| 229 | - | if e.display != nil { | 271 | + | fd := e.FD() |
| 230 | - | if fd, ok := e.display.nextFD(); ok { | 272 | + | if fd == nil { |
| 231 | - | return uintptr(fd) | 273 | + | _ = e.Uint32() |
| 232 | - | } | 274 | + | return 0 |
| 233 | } | 275 | } | ||
| 234 | - | // No descriptor was delivered: consume the placeholder argument. | 276 | + | n, _ := fd.Take() |
| 235 | - | _ = e.Uint32() | 277 | + | return uintptr(n) |
| 236 | - | return 0 | |||
| 237 | } | 278 | } | ||
| 238 | 279 | ||||
| 239 | // NewId reads a new object ID from the event | 280 | // NewId reads a new object ID from the event | ||
| @@ -278,32 +319,24 @@ type RegistryGlobalRemoveEvent struct { | |||||
| 278 | 319 | ||||
| 279 | // AddGlobalHandler adds a global handler to the registry | 320 | // AddGlobalHandler adds a global handler to the registry | ||
| 280 | func (r *Registry) AddGlobalHandler(handler RegistryGlobalHandler) { | 321 | func (r *Registry) AddGlobalHandler(handler RegistryGlobalHandler) { | ||
| 281 | - | // Store the handler to be called for ALL globals | 322 | + | if handler == nil { |
| 282 | - | r.mu.Lock() | 323 | + | return |
| 283 | - | defer r.mu.Unlock() | |||
| 284 | - | ||||
| 285 | - | // Initialize handlers map if needed | |||
| 286 | - | if r.handlers == nil { | |||
| 287 | - | r.handlers = make(map[string]GlobalHandler) | |||
| 288 | } | 324 | } | ||
| 289 | - | ||||
| 290 | r.AddHandler("*", func(registry *Registry, name uint32, version uint32) { | 325 | r.AddHandler("*", func(registry *Registry, name uint32, version uint32) { | ||
| 291 | - | global, ok := r.FindGlobalByName(name) | 326 | + | if global, ok := r.FindGlobalByName(name); ok { |
| 292 | - | if ok { | 327 | + | handler.HandleRegistryGlobal(RegistryGlobalEvent{Registry: r, Name: name, Interface: global.Interface, Version: version}) |
| 293 | - | event := RegistryGlobalEvent{ | |||
| 294 | - | Registry: r, | |||
| 295 | - | Name: name, | |||
| 296 | - | Interface: global.Interface, | |||
| 297 | - | Version: version, | |||
| 298 | - | } | |||
| 299 | - | handler.HandleRegistryGlobal(event) | |||
| 300 | } | 328 | } | ||
| 301 | }) | 329 | }) | ||
| 302 | } | 330 | } | ||
| 303 | 331 | ||||
| 304 | - | // AddGlobalRemoveHandler adds a global remove handler (placeholder) | 332 | + | // AddGlobalRemoveHandler registers a handler for registry removals. |
| 305 | func (r *Registry) AddGlobalRemoveHandler(handler RegistryGlobalRemoveHandler) { | 333 | func (r *Registry) AddGlobalRemoveHandler(handler RegistryGlobalRemoveHandler) { | ||
| 306 | - | // Would need to implement removal handling | 334 | + | if handler == nil { |
| 335 | + | return | |||
| 336 | + | } | |||
| 337 | + | r.mu.Lock() | |||
| 338 | + | r.removeHandlers = append(r.removeHandlers, handler) | |||
| 339 | + | r.mu.Unlock() | |||
| 307 | } | 340 | } | ||
| 308 | 341 | ||||
| 309 | // FindGlobalByName finds a global by its name ID | 342 | // FindGlobalByName finds a global by its name ID | ||
| @@ -340,342 +373,12 @@ func (d *Display) Sync() (Object, error) { | |||||
| 340 | display: d, | 373 | display: d, | ||
| 341 | } | 374 | } | ||
| 342 | 375 | ||||
| 343 | - | // Send sync request (opcode 0) | 376 | + | // Store before sending: the compositor may reply immediately. |
| 377 | + | d.context.Register(callback) | |||
| 344 | if err := d.SendRequest(1, 0, callbackID); err != nil { | 378 | if err := d.SendRequest(1, 0, callbackID); err != nil { | ||
| 379 | + | d.context.Unregister(callback) | |||
| 345 | return nil, err | 380 | return nil, err | ||
| 346 | } | 381 | } | ||
| 347 | 382 | ||||
| 348 | - | // Store the callback object | |||
| 349 | - | d.objects.Store(callbackID, callback) | |||
| 350 | - | ||||
| 351 | return callback, nil | 383 | return callback, nil | ||
| 352 | } | 384 | } | ||
| 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) | |||
| 534 | - | return nil, err | |||
| 535 | - | } | |||
| 536 | - | ||||
| 537 | - | return callback, nil | |||
| 538 | - | } | |||
| 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. | |
| @@ -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 fd_test.go +23 -48
| @@ -90,6 +90,8 @@ func newFDDisplay(t *testing.T, client *net.UnixConn) (*Display, *[]int) { | |||
| 90 | 90 | ||
| 91 | 91 | d := newDisplay(client) | |
| 92 | 92 | fds := []int{} | |
| 93 | + | d.RegisterEventSignature(7, 3, "uint,fd,") | |
| 94 | + | d.RegisterEventSignature(7, 4, "uint,") | |
| 93 | 95 | d.objects.Store(uint32(7), &fdRecordingProxy{ | |
| 94 | 96 | BaseProxy: BaseProxy{id: 7, context: d.context}, | |
| 95 | 97 | fds: &fds, | |
| @@ -146,49 +148,19 @@ func TestDisplayFD_Single(t *testing.T) { | |||
| 146 | 148 | readSentinel(t, r, "single-fd-sentinel") | |
| 147 | 149 | } | |
| 148 | 150 | ||
| 149 | - | func TestDisplayFD_MultipleInOneControlMessage(t *testing.T) { | |
| 151 | + | func TestDisplayFD_ExtraDescriptorRejected(t *testing.T) { | |
| 150 | 152 | 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() | |
| 159 | 155 | if err != nil { | |
| 160 | - | t.Fatalf("pipe: %v", err) | |
| 156 | + | t.Fatal(err) | |
| 161 | 157 | } | |
| 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) | |
| 185 | 163 | } | |
| 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") | |
| 192 | 164 | } | |
| 193 | 165 | ||
| 194 | 166 | func TestDisplayFD_EventWithoutFDsAfterOneWithFDs(t *testing.T) { | |
| @@ -304,9 +276,9 @@ func TestDisplayFD_TruncatedControlData(t *testing.T) { | |||
| 304 | 276 | } | |
| 305 | 277 | ||
| 306 | 278 | // A truncated control message must not leave descriptors queued. | |
| 307 | - | d.fdMu.Lock() | |
| 279 | + | d.recvMu.Lock() | |
| 308 | 280 | pending := len(d.pendingFDs) | |
| 309 | - | d.fdMu.Unlock() | |
| 281 | + | d.recvMu.Unlock() | |
| 310 | 282 | if pending != 0 { | |
| 311 | 283 | t.Fatalf("%d descriptors still queued after a truncated control message", pending) | |
| 312 | 284 | } | |
| @@ -315,6 +287,7 @@ func TestDisplayFD_TruncatedControlData(t *testing.T) { | |||
| 315 | 287 | func TestDisplayFD_UnconsumedDescriptorClosedOnShutdown(t *testing.T) { | |
| 316 | 288 | client, peer := unixSocketPair(t) | |
| 317 | 289 | d := newDisplay(client) | |
| 290 | + | d.RegisterEventSignature(7, 3, "uint,fd,") | |
| 318 | 291 | d.objects.Store(uint32(7), &fdIgnoringProxy{ | |
| 319 | 292 | BaseProxy: BaseProxy{id: 7, context: d.context}, | |
| 320 | 293 | }) | |
| @@ -333,12 +306,12 @@ func TestDisplayFD_UnconsumedDescriptorClosedOnShutdown(t *testing.T) { | |||
| 333 | 306 | t.Fatalf("Dispatch: %v", err) | |
| 334 | 307 | } | |
| 335 | 308 | ||
| 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) | |
| 339 | 312 | } | |
| 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) | |
| 342 | 315 | } | |
| 343 | 316 | ||
| 344 | 317 | // Closing the display must release it, so the read end sees EOF. | |
| @@ -421,6 +394,7 @@ func runFDLeakIteration(t *testing.T, devNull *os.File) { | |||
| 421 | 394 | ||
| 422 | 395 | client, peer := rawSocketPair(t) | |
| 423 | 396 | d := newDisplay(client) | |
| 397 | + | d.RegisterEventSignature(7, 3, "uint,fd,") | |
| 424 | 398 | d.objects.Store(uint32(7), &fdIgnoringProxy{ | |
| 425 | 399 | BaseProxy: BaseProxy{id: 7, context: d.context}, | |
| 426 | 400 | }) | |
| @@ -448,6 +422,7 @@ func runFDLeakIteration(t *testing.T, devNull *os.File) { | |||
| 448 | 422 | func TestDisplayFD_ParseFailureClosesQueuedDescriptor(t *testing.T) { | |
| 449 | 423 | client, peer := unixSocketPair(t) | |
| 450 | 424 | d := newDisplay(client) | |
| 425 | + | d.RegisterEventSignature(7, 3, "uint,fd,") | |
| 451 | 426 | d.objects.Store(uint32(7), &fdIgnoringProxy{ | |
| 452 | 427 | BaseProxy: BaseProxy{id: 7, context: d.context}, | |
| 453 | 428 | }) | |
| @@ -467,9 +442,9 @@ func TestDisplayFD_ParseFailureClosesQueuedDescriptor(t *testing.T) { | |||
| 467 | 442 | t.Fatalf("Dispatch error = %v, want %v", err, ErrMalformedFrame) | |
| 468 | 443 | } | |
| 469 | 444 | ||
| 470 | - | d.fdMu.Lock() | |
| 445 | + | d.recvMu.Lock() | |
| 471 | 446 | pending := len(d.pendingFDs) | |
| 472 | - | d.fdMu.Unlock() | |
| 447 | + | d.recvMu.Unlock() | |
| 473 | 448 | if pending != 0 { | |
| 474 | 449 | t.Fatalf("%d descriptors still queued after a malformed frame", pending) | |
| 475 | 450 | } | |
| @@ -90,6 +90,8 @@ func newFDDisplay(t *testing.T, client *net.UnixConn) (*Display, *[]int) { | |||||
| 90 | 90 | ||||
| 91 | d := newDisplay(client) | 91 | d := newDisplay(client) | ||
| 92 | fds := []int{} | 92 | fds := []int{} | ||
| 93 | + | d.RegisterEventSignature(7, 3, "uint,fd,") | |||
| 94 | + | d.RegisterEventSignature(7, 4, "uint,") | |||
| 93 | d.objects.Store(uint32(7), &fdRecordingProxy{ | 95 | d.objects.Store(uint32(7), &fdRecordingProxy{ | ||
| 94 | BaseProxy: BaseProxy{id: 7, context: d.context}, | 96 | BaseProxy: BaseProxy{id: 7, context: d.context}, | ||
| 95 | fds: &fds, | 97 | fds: &fds, | ||
| @@ -146,49 +148,19 @@ func TestDisplayFD_Single(t *testing.T) { | |||||
| 146 | readSentinel(t, r, "single-fd-sentinel") | 148 | readSentinel(t, r, "single-fd-sentinel") | ||
| 147 | } | 149 | } | ||
| 148 | 150 | ||||
| 149 | - | func TestDisplayFD_MultipleInOneControlMessage(t *testing.T) { | 151 | + | func TestDisplayFD_ExtraDescriptorRejected(t *testing.T) { |
| 150 | client, peer := unixSocketPair(t) | 152 | client, peer := unixSocketPair(t) | ||
| 151 | - | d, received := newFDDisplay(t, client) | 153 | + | d, _ := newFDDisplay(t, client) |
| 152 | - | 154 | + | r, w, err := os.Pipe() | |
| 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() | |||
| 159 | if err != nil { | 155 | if err != nil { | ||
| 160 | - | t.Fatalf("pipe: %v", err) | 156 | + | t.Fatal(err) |
| 161 | } | 157 | } | ||
| 162 | - | defer secondR.Close() | 158 | + | defer r.Close() |
| 163 | - | 159 | + | defer w.Close() | |
| 164 | - | sendMessageWithFDs(t, peer, message(7, 3, []byte{0, 0, 0, 0}), | 160 | + | sendMessageWithFDs(t, peer, message(7, 3, nil), []int{int(w.Fd()), int(w.Fd())}) |
| 165 | - | []int{int(firstW.Fd()), int(secondW.Fd())}) | 161 | + | if err := d.Dispatch(); !errors.Is(err, ErrMalformedFrame) { |
| 166 | - | firstW.Close() | 162 | + | t.Fatalf("extra FD: %v", err) |
| 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)) | |||
| 185 | } | 163 | } | ||
| 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") | |||
| 192 | } | 164 | } | ||
| 193 | 165 | ||||
| 194 | func TestDisplayFD_EventWithoutFDsAfterOneWithFDs(t *testing.T) { | 166 | func TestDisplayFD_EventWithoutFDsAfterOneWithFDs(t *testing.T) { | ||
| @@ -304,9 +276,9 @@ func TestDisplayFD_TruncatedControlData(t *testing.T) { | |||||
| 304 | } | 276 | } | ||
| 305 | 277 | ||||
| 306 | // A truncated control message must not leave descriptors queued. | 278 | // A truncated control message must not leave descriptors queued. | ||
| 307 | - | d.fdMu.Lock() | 279 | + | d.recvMu.Lock() |
| 308 | pending := len(d.pendingFDs) | 280 | pending := len(d.pendingFDs) | ||
| 309 | - | d.fdMu.Unlock() | 281 | + | d.recvMu.Unlock() |
| 310 | if pending != 0 { | 282 | if pending != 0 { | ||
| 311 | t.Fatalf("%d descriptors still queued after a truncated control message", pending) | 283 | t.Fatalf("%d descriptors still queued after a truncated control message", pending) | ||
| 312 | } | 284 | } | ||
| @@ -315,6 +287,7 @@ func TestDisplayFD_TruncatedControlData(t *testing.T) { | |||||
| 315 | func TestDisplayFD_UnconsumedDescriptorClosedOnShutdown(t *testing.T) { | 287 | func TestDisplayFD_UnconsumedDescriptorClosedOnShutdown(t *testing.T) { | ||
| 316 | client, peer := unixSocketPair(t) | 288 | client, peer := unixSocketPair(t) | ||
| 317 | d := newDisplay(client) | 289 | d := newDisplay(client) | ||
| 290 | + | d.RegisterEventSignature(7, 3, "uint,fd,") | |||
| 318 | d.objects.Store(uint32(7), &fdIgnoringProxy{ | 291 | d.objects.Store(uint32(7), &fdIgnoringProxy{ | ||
| 319 | BaseProxy: BaseProxy{id: 7, context: d.context}, | 292 | BaseProxy: BaseProxy{id: 7, context: d.context}, | ||
| 320 | }) | 293 | }) | ||
| @@ -333,12 +306,12 @@ func TestDisplayFD_UnconsumedDescriptorClosedOnShutdown(t *testing.T) { | |||||
| 333 | t.Fatalf("Dispatch: %v", err) | 306 | t.Fatalf("Dispatch: %v", err) | ||
| 334 | } | 307 | } | ||
| 335 | 308 | ||||
| 336 | - | // While the display still holds the descriptor the pipe stays open. | 309 | + | // An ignored FD is closed at the end of the event, not at shutdown. |
| 337 | - | if err := r.SetReadDeadline(time.Now().Add(200 * time.Millisecond)); err != nil { | 310 | + | if err := r.SetReadDeadline(time.Now().Add(2 * time.Second)); err != nil { |
| 338 | - | t.Fatalf("SetReadDeadline: %v", err) | 311 | + | t.Fatal(err) |
| 339 | } | 312 | } | ||
| 340 | - | if _, err := r.Read(make([]byte, 1)); !errors.Is(err, os.ErrDeadlineExceeded) { | 313 | + | if _, err := r.Read(make([]byte, 1)); !errors.Is(err, io.EOF) { |
| 341 | - | t.Fatalf("read with the display holding the descriptor = %v, want %v", err, os.ErrDeadlineExceeded) | 314 | + | t.Fatalf("ignored FD: %v", err) |
| 342 | } | 315 | } | ||
| 343 | 316 | ||||
| 344 | // Closing the display must release it, so the read end sees EOF. | 317 | // Closing the display must release it, so the read end sees EOF. | ||
| @@ -421,6 +394,7 @@ func runFDLeakIteration(t *testing.T, devNull *os.File) { | |||||
| 421 | 394 | ||||
| 422 | client, peer := rawSocketPair(t) | 395 | client, peer := rawSocketPair(t) | ||
| 423 | d := newDisplay(client) | 396 | d := newDisplay(client) | ||
| 397 | + | d.RegisterEventSignature(7, 3, "uint,fd,") | |||
| 424 | d.objects.Store(uint32(7), &fdIgnoringProxy{ | 398 | d.objects.Store(uint32(7), &fdIgnoringProxy{ | ||
| 425 | BaseProxy: BaseProxy{id: 7, context: d.context}, | 399 | BaseProxy: BaseProxy{id: 7, context: d.context}, | ||
| 426 | }) | 400 | }) | ||
| @@ -448,6 +422,7 @@ func runFDLeakIteration(t *testing.T, devNull *os.File) { | |||||
| 448 | func TestDisplayFD_ParseFailureClosesQueuedDescriptor(t *testing.T) { | 422 | func TestDisplayFD_ParseFailureClosesQueuedDescriptor(t *testing.T) { | ||
| 449 | client, peer := unixSocketPair(t) | 423 | client, peer := unixSocketPair(t) | ||
| 450 | d := newDisplay(client) | 424 | d := newDisplay(client) | ||
| 425 | + | d.RegisterEventSignature(7, 3, "uint,fd,") | |||
| 451 | d.objects.Store(uint32(7), &fdIgnoringProxy{ | 426 | d.objects.Store(uint32(7), &fdIgnoringProxy{ | ||
| 452 | BaseProxy: BaseProxy{id: 7, context: d.context}, | 427 | BaseProxy: BaseProxy{id: 7, context: d.context}, | ||
| 453 | }) | 428 | }) | ||
| @@ -467,9 +442,9 @@ func TestDisplayFD_ParseFailureClosesQueuedDescriptor(t *testing.T) { | |||||
| 467 | t.Fatalf("Dispatch error = %v, want %v", err, ErrMalformedFrame) | 442 | t.Fatalf("Dispatch error = %v, want %v", err, ErrMalformedFrame) | ||
| 468 | } | 443 | } | ||
| 469 | 444 | ||||
| 470 | - | d.fdMu.Lock() | 445 | + | d.recvMu.Lock() |
| 471 | pending := len(d.pendingFDs) | 446 | pending := len(d.pendingFDs) | ||
| 472 | - | d.fdMu.Unlock() | 447 | + | d.recvMu.Unlock() |
| 473 | if pending != 0 { | 448 | if pending != 0 { | ||
| 474 | t.Fatalf("%d descriptors still queued after a malformed frame", pending) | 449 | t.Fatalf("%d descriptors still queued after a malformed frame", pending) | ||
| 475 | } | 450 | } | ||
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 | + | } | |
| @@ -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 protocol/core/core_test.go +324 -0
| @@ -0,0 +1,324 @@ | |||
| 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 | + | } | |
| @@ -0,0 +1,324 @@ | |||||
| 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 | + | } | |||
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 | |
| @@ -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 +4023 -0 large diff (4023 lines changed): collapsed
M scm_linux.go +21 -20
| @@ -6,9 +6,12 @@ package wlturbo | |||
| 6 | 6 | import ( | |
| 7 | 7 | "errors" | |
| 8 | 8 | "fmt" | |
| 9 | - | "golang.org/x/sys/unix" | |
| 9 | + | "io" | |
| 10 | + | "net" | |
| 10 | 11 | "sync" | |
| 11 | 12 | "syscall" | |
| 13 | + | ||
| 14 | + | "golang.org/x/sys/unix" | |
| 12 | 15 | ) | |
| 13 | 16 | ||
| 14 | 17 | // maxRecvFDs bounds how many ancillary file descriptors a single read accepts. | |
| @@ -88,20 +91,9 @@ func parseFDs(oob []byte) ([]int, error) { | |||
| 88 | 91 | return fds, nil | |
| 89 | 92 | } | |
| 90 | 93 | ||
| 91 | - | // queueFDs appends descriptors to this connection's pending list. | |
| 92 | - | func (d *Display) queueFDs(fds []int) { | |
| 93 | - | if len(fds) == 0 { | |
| 94 | - | return | |
| 95 | - | } | |
| 96 | - | d.fdMu.Lock() | |
| 97 | - | d.pendingFDs = append(d.pendingFDs, fds...) | |
| 98 | - | d.fdMu.Unlock() | |
| 99 | - | } | |
| 100 | - | ||
| 101 | - | // nextFD removes and returns the oldest pending descriptor. | |
| 94 | + | // queueFDs is called only by the active Dispatch reader. | |
| 95 | + | func (d *Display) queueFDs(fds []int) { d.pendingFDs = append(d.pendingFDs, fds...) } | |
| 102 | 96 | func (d *Display) nextFD() (int, bool) { | |
| 103 | - | d.fdMu.Lock() | |
| 104 | - | defer d.fdMu.Unlock() | |
| 105 | 97 | if len(d.pendingFDs) == 0 { | |
| 106 | 98 | return -1, false | |
| 107 | 99 | } | |
| @@ -110,13 +102,10 @@ func (d *Display) nextFD() (int, bool) { | |||
| 110 | 102 | return fd, true | |
| 111 | 103 | } | |
| 112 | 104 | ||
| 113 | - | // closePendingFDs closes every descriptor this connection still holds. | |
| 105 | + | // closePendingFDs is called under recvMu (including during shutdown). | |
| 114 | 106 | func (d *Display) closePendingFDs() { | |
| 115 | - | d.fdMu.Lock() | |
| 116 | 107 | fds := d.pendingFDs | |
| 117 | 108 | d.pendingFDs = nil | |
| 118 | - | d.fdMu.Unlock() | |
| 119 | - | ||
| 120 | 109 | for _, fd := range fds { | |
| 121 | 110 | if fd >= 0 { | |
| 122 | 111 | _ = unix.Close(fd) | |
| @@ -124,20 +113,32 @@ func (d *Display) closePendingFDs() { | |||
| 124 | 113 | } | |
| 125 | 114 | } | |
| 126 | 115 | ||
| 116 | + | // CloseSentFD releases a descriptor after a complete successful Wayland send. | |
| 117 | + | func CloseSentFD(fd int) error { return unix.Close(fd) } | |
| 118 | + | ||
| 127 | 119 | // sendmsgWithFDs sends a message, attaching file descriptors when present. | |
| 128 | 120 | func (d *Display) sendmsgWithFDs(buf []byte, fds []int) error { | |
| 129 | 121 | d.sendMu.Lock() | |
| 130 | 122 | defer d.sendMu.Unlock() | |
| 131 | 123 | ||
| 124 | + | if d.closed.Load() { | |
| 125 | + | return net.ErrClosed | |
| 126 | + | } | |
| 132 | 127 | if len(fds) == 0 { | |
| 133 | - | _, err := d.conn.Write(buf) | |
| 128 | + | n, err := d.conn.Write(buf) | |
| 129 | + | if err == nil && n != len(buf) { | |
| 130 | + | return io.ErrShortWrite | |
| 131 | + | } | |
| 134 | 132 | return err | |
| 135 | 133 | } | |
| 136 | 134 | if d.unix == nil { | |
| 137 | 135 | return errors.New("wlturbo: cannot send file descriptors over a non-Unix connection") | |
| 138 | 136 | } | |
| 139 | 137 | ||
| 140 | - | _, _, err := d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil) | |
| 138 | + | n, _, err := d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil) | |
| 139 | + | if err == nil && n != len(buf) { | |
| 140 | + | return io.ErrShortWrite | |
| 141 | + | } | |
| 141 | 142 | return err | |
| 142 | 143 | } | |
| 143 | 144 | ||
| @@ -6,9 +6,12 @@ package wlturbo | |||||
| 6 | import ( | 6 | import ( | ||
| 7 | "errors" | 7 | "errors" | ||
| 8 | "fmt" | 8 | "fmt" | ||
| 9 | - | "golang.org/x/sys/unix" | 9 | + | "io" |
| 10 | + | "net" | |||
| 10 | "sync" | 11 | "sync" | ||
| 11 | "syscall" | 12 | "syscall" | ||
| 13 | + | ||||
| 14 | + | "golang.org/x/sys/unix" | |||
| 12 | ) | 15 | ) | ||
| 13 | 16 | ||||
| 14 | // maxRecvFDs bounds how many ancillary file descriptors a single read accepts. | 17 | // maxRecvFDs bounds how many ancillary file descriptors a single read accepts. | ||
| @@ -88,20 +91,9 @@ func parseFDs(oob []byte) ([]int, error) { | |||||
| 88 | return fds, nil | 91 | return fds, nil | ||
| 89 | } | 92 | } | ||
| 90 | 93 | ||||
| 91 | - | // queueFDs appends descriptors to this connection's pending list. | 94 | + | // queueFDs is called only by the active Dispatch reader. |
| 92 | - | func (d *Display) queueFDs(fds []int) { | 95 | + | func (d *Display) queueFDs(fds []int) { d.pendingFDs = append(d.pendingFDs, fds...) } |
| 93 | - | if len(fds) == 0 { | |||
| 94 | - | return | |||
| 95 | - | } | |||
| 96 | - | d.fdMu.Lock() | |||
| 97 | - | d.pendingFDs = append(d.pendingFDs, fds...) | |||
| 98 | - | d.fdMu.Unlock() | |||
| 99 | - | } | |||
| 100 | - | ||||
| 101 | - | // nextFD removes and returns the oldest pending descriptor. | |||
| 102 | func (d *Display) nextFD() (int, bool) { | 96 | func (d *Display) nextFD() (int, bool) { | ||
| 103 | - | d.fdMu.Lock() | |||
| 104 | - | defer d.fdMu.Unlock() | |||
| 105 | if len(d.pendingFDs) == 0 { | 97 | if len(d.pendingFDs) == 0 { | ||
| 106 | return -1, false | 98 | return -1, false | ||
| 107 | } | 99 | } | ||
| @@ -110,13 +102,10 @@ func (d *Display) nextFD() (int, bool) { | |||||
| 110 | return fd, true | 102 | return fd, true | ||
| 111 | } | 103 | } | ||
| 112 | 104 | ||||
| 113 | - | // closePendingFDs closes every descriptor this connection still holds. | 105 | + | // closePendingFDs is called under recvMu (including during shutdown). |
| 114 | func (d *Display) closePendingFDs() { | 106 | func (d *Display) closePendingFDs() { | ||
| 115 | - | d.fdMu.Lock() | |||
| 116 | fds := d.pendingFDs | 107 | fds := d.pendingFDs | ||
| 117 | d.pendingFDs = nil | 108 | d.pendingFDs = nil | ||
| 118 | - | d.fdMu.Unlock() | |||
| 119 | - | ||||
| 120 | for _, fd := range fds { | 109 | for _, fd := range fds { | ||
| 121 | if fd >= 0 { | 110 | if fd >= 0 { | ||
| 122 | _ = unix.Close(fd) | 111 | _ = unix.Close(fd) | ||
| @@ -124,20 +113,32 @@ func (d *Display) closePendingFDs() { | |||||
| 124 | } | 113 | } | ||
| 125 | } | 114 | } | ||
| 126 | 115 | ||||
| 116 | + | // CloseSentFD releases a descriptor after a complete successful Wayland send. | |||
| 117 | + | func CloseSentFD(fd int) error { return unix.Close(fd) } | |||
| 118 | + | ||||
| 127 | // sendmsgWithFDs sends a message, attaching file descriptors when present. | 119 | // sendmsgWithFDs sends a message, attaching file descriptors when present. | ||
| 128 | func (d *Display) sendmsgWithFDs(buf []byte, fds []int) error { | 120 | func (d *Display) sendmsgWithFDs(buf []byte, fds []int) error { | ||
| 129 | d.sendMu.Lock() | 121 | d.sendMu.Lock() | ||
| 130 | defer d.sendMu.Unlock() | 122 | defer d.sendMu.Unlock() | ||
| 131 | 123 | ||||
| 124 | + | if d.closed.Load() { | |||
| 125 | + | return net.ErrClosed | |||
| 126 | + | } | |||
| 132 | if len(fds) == 0 { | 127 | if len(fds) == 0 { | ||
| 133 | - | _, err := d.conn.Write(buf) | 128 | + | n, err := d.conn.Write(buf) |
| 129 | + | if err == nil && n != len(buf) { | |||
| 130 | + | return io.ErrShortWrite | |||
| 131 | + | } | |||
| 134 | return err | 132 | return err | ||
| 135 | } | 133 | } | ||
| 136 | if d.unix == nil { | 134 | if d.unix == nil { | ||
| 137 | return errors.New("wlturbo: cannot send file descriptors over a non-Unix connection") | 135 | return errors.New("wlturbo: cannot send file descriptors over a non-Unix connection") | ||
| 138 | } | 136 | } | ||
| 139 | 137 | ||||
| 140 | - | _, _, err := d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil) | 138 | + | n, _, err := d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil) |
| 139 | + | if err == nil && n != len(buf) { | |||
| 140 | + | return io.ErrShortWrite | |||
| 141 | + | } | |||
| 141 | return err | 142 | return err | ||
| 142 | } | 143 | } | ||
| 143 | 144 | ||||
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 | + | } | |
| @@ -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 +10 -5
| @@ -92,6 +92,11 @@ func newChunkDisplay(chunks [][]byte) (*Display, *[]EventRecord) { | |||
| 92 | 92 | BaseProxy: BaseProxy{id: 7, context: d.context}, | |
| 93 | 93 | records: &records, | |
| 94 | 94 | }) | |
| 95 | + | for _, opcode := range []uint16{3, 4, 5} { | |
| 96 | + | signature := "uint," | |
| 97 | + | if opcode == 3 { signature = "uint,uint," } | |
| 98 | + | d.RegisterEventSignature(7, opcode, signature) | |
| 99 | + | } | |
| 95 | 100 | return d, &records | |
| 96 | 101 | } | |
| 97 | 102 | ||
| @@ -132,7 +137,7 @@ func TestDisplayDispatch_Fragmented(t *testing.T) { | |||
| 132 | 137 | } | |
| 133 | 138 | ||
| 134 | 139 | func TestDisplayDispatch_Coalesced(t *testing.T) { | |
| 135 | - | first := message(7, 3, []byte{0x11, 0x12, 0x13, 0x14}) | |
| 140 | + | first := message(7, 4, []byte{0x11, 0x12, 0x13, 0x14}) | |
| 136 | 141 | second := message(7, 4, []byte{0x21, 0x22, 0x23, 0x24}) | |
| 137 | 142 | ||
| 138 | 143 | d, records := newChunkDisplay([][]byte{append(append([]byte{}, first...), second...)}) | |
| @@ -146,7 +151,7 @@ func TestDisplayDispatch_Coalesced(t *testing.T) { | |||
| 146 | 151 | } | |
| 147 | 152 | ||
| 148 | 153 | want := []EventRecord{ | |
| 149 | - | {Object: 7, Opcode: 3, Body: []byte{0x11, 0x12, 0x13, 0x14}}, | |
| 154 | + | {Object: 7, Opcode: 4, Body: []byte{0x11, 0x12, 0x13, 0x14}}, | |
| 150 | 155 | {Object: 7, Opcode: 4, Body: []byte{0x21, 0x22, 0x23, 0x24}}, | |
| 151 | 156 | } | |
| 152 | 157 | if !reflect.DeepEqual(*records, want) { | |
| @@ -157,7 +162,7 @@ func TestDisplayDispatch_Coalesced(t *testing.T) { | |||
| 157 | 162 | func TestDisplayDispatch_HalfCoalesced(t *testing.T) { | |
| 158 | 163 | // A read boundary that ends between the header and the body of the second | |
| 159 | 164 | // message must not be mistaken for a frame boundary. | |
| 160 | - | whole := append(message(7, 3, []byte{0x01, 0x02, 0x03, 0x04}), | |
| 165 | + | whole := append(message(7, 4, []byte{0x01, 0x02, 0x03, 0x04}), | |
| 161 | 166 | message(7, 5, []byte{0x05, 0x06, 0x07, 0x08})...) | |
| 162 | 167 | ||
| 163 | 168 | split := HeaderSize + 4 + 4 // mid-body of the second message | |
| @@ -171,7 +176,7 @@ func TestDisplayDispatch_HalfCoalesced(t *testing.T) { | |||
| 171 | 176 | } | |
| 172 | 177 | ||
| 173 | 178 | want := []EventRecord{ | |
| 174 | - | {Object: 7, Opcode: 3, Body: []byte{0x01, 0x02, 0x03, 0x04}}, | |
| 179 | + | {Object: 7, Opcode: 4, Body: []byte{0x01, 0x02, 0x03, 0x04}}, | |
| 175 | 180 | {Object: 7, Opcode: 5, Body: []byte{0x05, 0x06, 0x07, 0x08}}, | |
| 176 | 181 | } | |
| 177 | 182 | if !reflect.DeepEqual(*records, want) { | |
| @@ -299,7 +304,7 @@ func TestDisplayDispatch_PeerClose(t *testing.T) { | |||
| 299 | 304 | }, | |
| 300 | 305 | { | |
| 301 | 306 | name: "closed after a complete message", | |
| 302 | - | chunks: [][]byte{message(7, 3, []byte{0x01, 0x02, 0x03, 0x04})}, | |
| 307 | + | chunks: [][]byte{message(7, 4, []byte{0x01, 0x02, 0x03, 0x04})}, | |
| 303 | 308 | }, | |
| 304 | 309 | } | |
| 305 | 310 | ||
| @@ -92,6 +92,11 @@ func newChunkDisplay(chunks [][]byte) (*Display, *[]EventRecord) { | |||||
| 92 | BaseProxy: BaseProxy{id: 7, context: d.context}, | 92 | BaseProxy: BaseProxy{id: 7, context: d.context}, | ||
| 93 | records: &records, | 93 | records: &records, | ||
| 94 | }) | 94 | }) | ||
| 95 | + | for _, opcode := range []uint16{3, 4, 5} { | |||
| 96 | + | signature := "uint," | |||
| 97 | + | if opcode == 3 { signature = "uint,uint," } | |||
| 98 | + | d.RegisterEventSignature(7, opcode, signature) | |||
| 99 | + | } | |||
| 95 | return d, &records | 100 | return d, &records | ||
| 96 | } | 101 | } | ||
| 97 | 102 | ||||
| @@ -132,7 +137,7 @@ func TestDisplayDispatch_Fragmented(t *testing.T) { | |||||
| 132 | } | 137 | } | ||
| 133 | 138 | ||||
| 134 | func TestDisplayDispatch_Coalesced(t *testing.T) { | 139 | func TestDisplayDispatch_Coalesced(t *testing.T) { | ||
| 135 | - | first := message(7, 3, []byte{0x11, 0x12, 0x13, 0x14}) | 140 | + | first := message(7, 4, []byte{0x11, 0x12, 0x13, 0x14}) |
| 136 | second := message(7, 4, []byte{0x21, 0x22, 0x23, 0x24}) | 141 | second := message(7, 4, []byte{0x21, 0x22, 0x23, 0x24}) | ||
| 137 | 142 | ||||
| 138 | d, records := newChunkDisplay([][]byte{append(append([]byte{}, first...), second...)}) | 143 | d, records := newChunkDisplay([][]byte{append(append([]byte{}, first...), second...)}) | ||
| @@ -146,7 +151,7 @@ func TestDisplayDispatch_Coalesced(t *testing.T) { | |||||
| 146 | } | 151 | } | ||
| 147 | 152 | ||||
| 148 | want := []EventRecord{ | 153 | want := []EventRecord{ | ||
| 149 | - | {Object: 7, Opcode: 3, Body: []byte{0x11, 0x12, 0x13, 0x14}}, | 154 | + | {Object: 7, Opcode: 4, Body: []byte{0x11, 0x12, 0x13, 0x14}}, |
| 150 | {Object: 7, Opcode: 4, Body: []byte{0x21, 0x22, 0x23, 0x24}}, | 155 | {Object: 7, Opcode: 4, Body: []byte{0x21, 0x22, 0x23, 0x24}}, | ||
| 151 | } | 156 | } | ||
| 152 | if !reflect.DeepEqual(*records, want) { | 157 | if !reflect.DeepEqual(*records, want) { | ||
| @@ -157,7 +162,7 @@ func TestDisplayDispatch_Coalesced(t *testing.T) { | |||||
| 157 | func TestDisplayDispatch_HalfCoalesced(t *testing.T) { | 162 | func TestDisplayDispatch_HalfCoalesced(t *testing.T) { | ||
| 158 | // A read boundary that ends between the header and the body of the second | 163 | // A read boundary that ends between the header and the body of the second | ||
| 159 | // message must not be mistaken for a frame boundary. | 164 | // message must not be mistaken for a frame boundary. | ||
| 160 | - | whole := append(message(7, 3, []byte{0x01, 0x02, 0x03, 0x04}), | 165 | + | whole := append(message(7, 4, []byte{0x01, 0x02, 0x03, 0x04}), |
| 161 | message(7, 5, []byte{0x05, 0x06, 0x07, 0x08})...) | 166 | message(7, 5, []byte{0x05, 0x06, 0x07, 0x08})...) | ||
| 162 | 167 | ||||
| 163 | split := HeaderSize + 4 + 4 // mid-body of the second message | 168 | split := HeaderSize + 4 + 4 // mid-body of the second message | ||
| @@ -171,7 +176,7 @@ func TestDisplayDispatch_HalfCoalesced(t *testing.T) { | |||||
| 171 | } | 176 | } | ||
| 172 | 177 | ||||
| 173 | want := []EventRecord{ | 178 | want := []EventRecord{ | ||
| 174 | - | {Object: 7, Opcode: 3, Body: []byte{0x01, 0x02, 0x03, 0x04}}, | 179 | + | {Object: 7, Opcode: 4, Body: []byte{0x01, 0x02, 0x03, 0x04}}, |
| 175 | {Object: 7, Opcode: 5, Body: []byte{0x05, 0x06, 0x07, 0x08}}, | 180 | {Object: 7, Opcode: 5, Body: []byte{0x05, 0x06, 0x07, 0x08}}, | ||
| 176 | } | 181 | } | ||
| 177 | if !reflect.DeepEqual(*records, want) { | 182 | if !reflect.DeepEqual(*records, want) { | ||
| @@ -299,7 +304,7 @@ func TestDisplayDispatch_PeerClose(t *testing.T) { | |||||
| 299 | }, | 304 | }, | ||
| 300 | { | 305 | { | ||
| 301 | name: "closed after a complete message", | 306 | name: "closed after a complete message", | ||
| 302 | - | chunks: [][]byte{message(7, 3, []byte{0x01, 0x02, 0x03, 0x04})}, | 307 | + | chunks: [][]byte{message(7, 4, []byte{0x01, 0x02, 0x03, 0x04})}, |
| 303 | }, | 308 | }, | ||
| 304 | } | 309 | } | ||
| 305 | 310 | ||||
M wl/wl.go +2 -18
| @@ -21,14 +21,7 @@ type ( | |||
| 21 | 21 | RegistryGlobalRemoveHandler = wlturbo.RegistryGlobalRemoveHandler | |
| 22 | 22 | RegistryGlobalEvent = wlturbo.RegistryGlobalEvent | |
| 23 | 23 | 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 | |
| 32 | 25 | ||
| 33 | 26 | // Transport failures. Without these, a caller that imports this shim has to | |
| 34 | 27 | // import the root package as well just to classify an error. | |
| @@ -40,10 +33,8 @@ type ( | |||
| 40 | 33 | var ( | |
| 41 | 34 | Connect = wlturbo.Connect | |
| 42 | 35 | NewFixed = wlturbo.NewFixed | |
| 43 | - | NewSeat = wlturbo.NewSeat | |
| 44 | 36 | NewContext = wlturbo.NewContext | |
| 45 | - | NewSurface = wlturbo.NewSurface | |
| 46 | - | NewCompositor = wlturbo.NewCompositor | |
| 37 | + | CloseSentFD = wlturbo.CloseSentFD | |
| 47 | 38 | CreateAnonymousFile = wlturbo.CreateAnonymousFile | |
| 48 | 39 | MapMemory = wlturbo.MapMemory | |
| 49 | 40 | UnmapMemory = wlturbo.UnmapMemory | |
| @@ -57,10 +48,3 @@ var ( | |||
| 57 | 48 | ErrUnknownOpcode = wlturbo.ErrUnknownOpcode | |
| 58 | 49 | ErrDisplayError = wlturbo.ErrDisplayError | |
| 59 | 50 | ) | |
| 60 | - | ||
| 61 | - | // Seat capability constants | |
| 62 | - | const ( | |
| 63 | - | SeatCapabilityPointer = wlturbo.SeatCapabilityPointer | |
| 64 | - | SeatCapabilityKeyboard = wlturbo.SeatCapabilityKeyboard | |
| 65 | - | SeatCapabilityTouch = wlturbo.SeatCapabilityTouch | |
| 66 | - | ) | |
| @@ -21,14 +21,7 @@ type ( | |||||
| 21 | RegistryGlobalRemoveHandler = wlturbo.RegistryGlobalRemoveHandler | 21 | RegistryGlobalRemoveHandler = wlturbo.RegistryGlobalRemoveHandler | ||
| 22 | RegistryGlobalEvent = wlturbo.RegistryGlobalEvent | 22 | RegistryGlobalEvent = wlturbo.RegistryGlobalEvent | ||
| 23 | RegistryGlobalRemoveEvent = wlturbo.RegistryGlobalRemoveEvent | 23 | RegistryGlobalRemoveEvent = wlturbo.RegistryGlobalRemoveEvent | ||
| 24 | - | Seat = wlturbo.Seat | 24 | + | OwnedFD = wlturbo.OwnedFD |
| 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 | |||
| 32 | 25 | ||||
| 33 | // Transport failures. Without these, a caller that imports this shim has to | 26 | // Transport failures. Without these, a caller that imports this shim has to | ||
| 34 | // import the root package as well just to classify an error. | 27 | // import the root package as well just to classify an error. | ||
| @@ -40,10 +33,8 @@ type ( | |||||
| 40 | var ( | 33 | var ( | ||
| 41 | Connect = wlturbo.Connect | 34 | Connect = wlturbo.Connect | ||
| 42 | NewFixed = wlturbo.NewFixed | 35 | NewFixed = wlturbo.NewFixed | ||
| 43 | - | NewSeat = wlturbo.NewSeat | |||
| 44 | NewContext = wlturbo.NewContext | 36 | NewContext = wlturbo.NewContext | ||
| 45 | - | NewSurface = wlturbo.NewSurface | 37 | + | CloseSentFD = wlturbo.CloseSentFD |
| 46 | - | NewCompositor = wlturbo.NewCompositor | |||
| 47 | CreateAnonymousFile = wlturbo.CreateAnonymousFile | 38 | CreateAnonymousFile = wlturbo.CreateAnonymousFile | ||
| 48 | MapMemory = wlturbo.MapMemory | 39 | MapMemory = wlturbo.MapMemory | ||
| 49 | UnmapMemory = wlturbo.UnmapMemory | 40 | UnmapMemory = wlturbo.UnmapMemory | ||
| @@ -57,10 +48,3 @@ var ( | |||||
| 57 | ErrUnknownOpcode = wlturbo.ErrUnknownOpcode | 48 | ErrUnknownOpcode = wlturbo.ErrUnknownOpcode | ||
| 58 | ErrDisplayError = wlturbo.ErrDisplayError | 49 | ErrDisplayError = wlturbo.ErrDisplayError | ||
| 59 | ) | 50 | ) | ||
| 60 | - | ||||
| 61 | - | // Seat capability constants | |||
| 62 | - | const ( | |||
| 63 | - | SeatCapabilityPointer = wlturbo.SeatCapabilityPointer | |||
| 64 | - | SeatCapabilityKeyboard = wlturbo.SeatCapabilityKeyboard | |||
| 65 | - | SeatCapabilityTouch = wlturbo.SeatCapabilityTouch | |||
| 66 | - | ) | |||