WLTurbo
A performance-focused, low-level Wayland client runtime for Go.
Overview
WLTurbo is the transport layer: connection, wire framing, descriptor passing, object lifecycle and the bootstrap registry. It is not a complete Wayland client. It has no windows, surfaces, seats, input handling or rendering, and it knows no protocol beyond the bootstrap objects. Protocol bindings are provided separately (see Protocol bindings).
Where it fits
Application or toolkit windows, input, rendering, policy
└─ Protocol bindings generated code, one package per protocol
└─ WLTurbo transport and objects (this module)
Use WLTurbo directly to speak raw Wayland or to write your own bindings or toolkit. Ready-made bindings are available in go-wayland-bindings.
Architecture
WLTurbo provides the foundational Wayland client infrastructure:
- Bootstrap Objects: Display and Registry
- Event Dispatching: Efficient routing of Wayland events to handlers
- Connection Management: Unix socket communication with the compositor
- Memory Management: Shared memory support via file descriptor passing
Applications own rendering, input interpretation, capability fallbacks and desktop policy. WLTurbo is client-side only, not a compositor-side server implementation.
Protocol bindings
Generated protocol bindings are not part of this module. They live in github.com/bnema/go-wayland-bindings, one package per protocol under client/<pkg>, for example client/wayland, client/xdgshell and client/wlrlayershell:
import "github.com/bnema/go-wayland-bindings/client/xdgshell"
Those packages are built on this runtime. A binding's presence does not imply that the compositor implements it.
After display.Roundtrip() discovers globals, call display.Registry().BindNegotiated(interfaceName, supportedVersion, proxy) to bind at the lesser of the advertised and supported versions. It returns the negotiated version; use errors.Is(err, wlturbo.ErrGlobalNotFound) to handle absent optional globals.
Features
- Stream framing: messages split across or coalesced within socket reads are framed exactly; event bodies are not copied.
- Descriptor ownership: received FDs belong to the event that declares them and are closed if a handler does not take them.
- Object lifecycle: destroyed objects stay as zombies until
delete_id, so events already in flight are dropped instead of failing the connection.
- Version checks: requests newer than the bound object version return
ErrVersionTooLow before anything is sent.
- Allocation-free numeric paths: numeric requests sent with
Context.RequestArgs without object creation or descriptors, and typed numeric events, do not allocate after warm-up. Object creation, received strings and descriptors have separate allocation costs.
Quick Start
This opens a connection and dispatches events. Binding globals needs the protocol packages from go-wayland-bindings.
package main
import (
"github.com/bnema/wlturbo/wl"
)
func main() {
// Connect to Wayland display
display, err := wl.Connect("")
if err != nil {
panic(err)
}
defer display.Close()
// Basic event loop. Handlers run inside Dispatch and must not call
// Dispatch or Roundtrip on the same display.
for {
if err := display.Dispatch(); err != nil {
break
}
}
}
For higher-level device control, libwldevices-go builds on WLTurbo.
Development checks
GOWORK=off go test ./...
GOWORK=off go test -race ./...
GOWORK=off go vet ./...
GOWORK=off go test -run '^$' -bench . -benchmem
Socketpair tests exercise wire messages, versions, object lifecycle and file descriptors without a compositor.
Requirements
- Go 1.27+
- Linux with Wayland compositor
- Unix sockets support
License
MIT; see LICENSE.
Contributing
Contributions welcome! Areas of focus:
- Performance improvements
- Transport correctness and hardening
- Documentation and examples
- Benchmark suite
source
1# WLTurbo
2
3A performance-focused, low-level Wayland client runtime for Go.
4
5## Overview
6
7WLTurbo is the transport layer: connection, wire framing, descriptor passing, object lifecycle and the bootstrap registry. It is not a complete Wayland client. It has no windows, surfaces, seats, input handling or rendering, and it knows no protocol beyond the bootstrap objects. Protocol bindings are provided separately (see [Protocol bindings](#protocol-bindings)).
8
9## Where it fits
10
11```
12Application or toolkit windows, input, rendering, policy
13└─ Protocol bindings generated code, one package per protocol
14 └─ WLTurbo transport and objects (this module)
15```
16
17Use WLTurbo directly to speak raw Wayland or to write your own bindings or toolkit. Ready-made bindings are available in [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings).
18
19## Architecture
20
21WLTurbo provides the foundational Wayland client infrastructure:
22
23- **Bootstrap Objects**: Display and Registry
24- **Event Dispatching**: Efficient routing of Wayland events to handlers
25- **Connection Management**: Unix socket communication with the compositor
26- **Memory Management**: Shared memory support via file descriptor passing
27
28Applications own rendering, input interpretation, capability fallbacks and desktop policy. WLTurbo is client-side only, not a compositor-side server implementation.
29
30## Protocol bindings
31
32Generated protocol bindings are not part of this module. They live in [github.com/bnema/go-wayland-bindings](https://github.com/bnema/go-wayland-bindings), one package per protocol under `client/<pkg>`, for example `client/wayland`, `client/xdgshell` and `client/wlrlayershell`:
33
34```go
35import "github.com/bnema/go-wayland-bindings/client/xdgshell"
36```
37
38Those packages are built on this runtime. A binding's presence does not imply that the compositor implements it.
39
40After `display.Roundtrip()` discovers globals, call `display.Registry().BindNegotiated(interfaceName, supportedVersion, proxy)` to bind at the lesser of the advertised and supported versions. It returns the negotiated version; use `errors.Is(err, wlturbo.ErrGlobalNotFound)` to handle absent optional globals.
41
42## Features
43
44- **Stream framing**: messages split across or coalesced within socket reads are framed exactly; event bodies are not copied.
45- **Descriptor ownership**: received FDs belong to the event that declares them and are closed if a handler does not take them.
46- **Object lifecycle**: destroyed objects stay as zombies until `delete_id`, so events already in flight are dropped instead of failing the connection.
47- **Version checks**: requests newer than the bound object version return `ErrVersionTooLow` before anything is sent.
48- **Allocation-free numeric paths**: numeric requests sent with `Context.RequestArgs` without object creation or descriptors, and typed numeric events, do not allocate after warm-up. Object creation, received strings and descriptors have separate allocation costs.
49
50## Quick Start
51
52This opens a connection and dispatches events. Binding globals needs the protocol packages from `go-wayland-bindings`.
53
54```go
55package main
56
57import (
58 "github.com/bnema/wlturbo/wl"
59)
60
61func main() {
62 // Connect to Wayland display
63 display, err := wl.Connect("")
64 if err != nil {
65 panic(err)
66 }
67 defer display.Close()
68
69 // Basic event loop. Handlers run inside Dispatch and must not call
70 // Dispatch or Roundtrip on the same display.
71 for {
72 if err := display.Dispatch(); err != nil {
73 break
74 }
75 }
76}
77```
78
79For higher-level device control, [libwldevices-go](https://github.com/bnema/libwldevices-go) builds on WLTurbo.
80
81## Development checks
82
83```sh
84GOWORK=off go test ./...
85GOWORK=off go test -race ./...
86GOWORK=off go vet ./...
87GOWORK=off go test -run '^$' -bench . -benchmem
88```
89
90Socketpair tests exercise wire messages, versions, object lifecycle and file descriptors without a compositor.
91
92## Requirements
93
94- **Go 1.27+**
95- **Linux** with Wayland compositor
96- **Unix sockets** support
97
98## License
99
100MIT; see [LICENSE](LICENSE).
101
102## Contributing
103
104Contributions welcome! Areas of focus:
105- Performance improvements
106- Transport correctness and hardening
107- Documentation and examples
108- Benchmark suite