Project 1: Bare-Metal Wayland Client
Project 1: Bare-Metal Wayland Client
Project Overview
| Attribute | Value |
|---|---|
| Difficulty | Intermediate (Level 3) |
| Time Estimate | 1-2 weeks |
| Programming Language | C |
| Knowledge Area | Graphics / Window Systems |
| Main Book | โThe Wayland Bookโ by Drew DeVault |
| Coolness Level | Level 4: Hardcore Tech Flex |
| Business Potential | Resume Gold |
What youโll build: A Wayland client from scratch that displays a colored window using only libwayland-client and shared memoryโno toolkits, no helpers.
Why it teaches Wayland: Forces you to understand the entire client lifecycle: connecting to the compositor, negotiating protocols via wl_registry, creating surfaces, attaching buffers, and handling the frame callback loop.
Learning Objectives
By completing this project, you will be able to:
- Explain the Wayland protocol model - Describe how Waylandโs asynchronous, object-oriented protocol differs from X11โs request/reply model
- Implement a Wayland client from scratch - Write C code that connects to a compositor, negotiates interfaces, and displays pixels
- Manage shared memory buffers - Use POSIX shared memory (
memfd_create,mmap) for zero-copy buffer sharing - Handle the xdg-shell lifecycle - Implement the configure/ack/commit cycle correctly
- Debug Wayland applications - Use
WAYLAND_DEBUG=1to trace protocol messages - Compare client-side vs server-side rendering - Understand why Wayland puts rendering responsibility on clients
The Core Question Youโre Answering
โHow does a graphical application actually display pixels on screen in a modern Linux desktop, and what is the conversation between my program and the compositor?โ
Before you write any code, sit with this question. Most developers think โGTK draws my windowโ or โX11 shows my pixels,โ but they canโt explain what that actually means at the protocol level.
The deeper question beneath this:
โWhy does Wayland exist? What was so broken about X11 that the entire Linux graphics stack was redesigned from scratch?โ
By building a bare-metal Wayland client, youโll discover that:
- Wayland is asynchronous: You request things and get callbacks, not immediate responses
- Wayland is stateless on the compositor side: The compositor doesnโt store your window contentsโyou do
- Wayland is about buffer passing: You render pixels in shared memory; the compositor just displays them
- Wayland is object-oriented: Everything is an object (surface, buffer, seat) with interfaces and events
This project answers the fundamental question: โWhat is the minimal conversation needed between an application and a display server to show a window?โ
Deep Theoretical Foundation
1. Display Server Architecture: X11 vs Wayland
Understanding why Wayland exists requires understanding what was wrong with X11:
X11 Architecture (Complex 3-Layer Stack):
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Application โ โ Application โ โ Application โ
โ (Client) โ โ (Client) โ โ (Client) โ
โโโโโโโโโโฌโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโ
โ โ โ
โ X11 Protocol (Network) โ โ
โ - Draw requests โ โ
โ - Event messages โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ X Server โ
โ - Manages resources โ
โ - Executes draw commands โ
โ - Routes input events โ
โ - Handles network protocol โ
โ - Stores window contents โ
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Rendered frames
โ
โโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Compositor (Separate Process) โ
โ - Reads from X server โ
โ - Applies effects โ
โ - Composites to framebuffer โ
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GPU / Display Hardware โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Problems with X11:
โข Extensive IPC (client โ X server โ compositor)
โข X server as middleman adds latency
โข Security: any client can spy on other windows
โข X server stores window contents (memory overhead)
โข Network protocol overhead even for local apps
Wayland Architecture (Direct, Simple 2-Layer):
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Application โ โ Application โ โ Application โ
โ (Client) โ โ (Client) โ โ (Client) โ
โ โ โ โ โ โ
โ โโโโโโโโโโโโโ โ โ โโโโโโโโโโโโโ โ โ โโโโโโโโโโโโโ โ
โ โ Renders โ โ โ โ Renders โ โ โ โ Renders โ โ
โ โ Own Windowโ โ โ โ Own Windowโ โ โ โ Own Windowโ โ
โ โ to Buffer โ โ โ โ to Buffer โ โ โ โ to Buffer โ โ
โ โโโโโโโฌโโโโโโ โ โ โโโโโโโฌโโโโโโ โ โ โโโโโโโฌโโโโโโ โ
โโโโโโโโโโผโโโโโโโโโ โโโโโโโโโโผโโโโโโโโโ โโโโโโโโโโผโโโโโโโโโ
โ โ โ
โ Wayland Protocol โ โ
โ - Share buffer FD โ โ
โ - Input events โ โ
โ - Surface config โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Wayland Compositor (All-in-One) โ
โ โ
โ Display Server + Window Manager + Effects โ
โ โ
โ โข Receives client buffers (zero-copy) โ
โ โข Manages window positions & focus โ
โ โข Routes input to correct client โ
โ โข Composites buffers to screen โ
โ โข Controls DRM/KMS (display hardware) โ
โ โข Enforces security isolation โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Direct GPU access
โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GPU / Display Hardware โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Benefits of Wayland:
โ Clients render directly โ no middleman
โ Zero-copy buffer sharing โ better performance
โ Security by design โ input isolation
โ Compositor has full control โ smooth animations
โ Simpler protocol โ easier to implement
โ No network overhead for local apps
2. The Wayland Object Model
Wayland uses an object-oriented protocol where everything is an object with:
- A unique ID within the connection
- An interface (like a class)
- Requests (methods you call on the object)
- Events (callbacks the object fires)
WAYLAND OBJECT HIERARCHY
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
wl_display (ID=1, always exists)
โ
โโโ wl_registry (ID=2, requested from display)
โ โ
โ โโโ wl_compositor (global, bind to create surfaces)
โ โ โ
โ โ โโโ wl_surface (created from compositor)
โ โ โ
โ โ โโโ xdg_surface (role assignment)
โ โ โ
โ โ โโโ xdg_toplevel (window behavior)
โ โ
โ โโโ wl_shm (global, shared memory management)
โ โ โ
โ โ โโโ wl_shm_pool (created from shm)
โ โ โ
โ โ โโโ wl_buffer (created from pool)
โ โ
โ โโโ xdg_wm_base (global, xdg-shell entry point)
โ โ
โ โโโ wl_seat (global, input device group)
โ โ โ
โ โ โโโ wl_keyboard
โ โ โโโ wl_pointer
โ โ โโโ wl_touch
โ โ
โ โโโ wl_output (global, each monitor)
โ
โโโ wl_callback (one-shot callback objects)
3. Shared Memory and Zero-Copy Rendering
Wayland achieves high performance through zero-copy buffer sharing:
SHARED MEMORY BUFFER FLOW
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Client Process Compositor Process
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ โ
โ 1. Create shared memory: โ โ โ
โ fd = memfd_create() โ โ โ
โ ftruncate(fd, size) โ โ โ
โ โ โ โ
โ 2. Map into client memory: โ โ โ
โ data = mmap(fd) โ โ โ
โ โโโโโโโโโโโโโโโโโโโ โ โ โ
โ โ Shared Memory โโโโโโโโผโโโโโโโโผโโโโโโโโโโโโโโโโโโโโ โ
โ โ (Client's View) โ โ โ โ โ
โ โ pixels[0..n] โ โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโ โ โ โ โ
โ โ โ โ โ โ
โ 3. Create wl_shm_pool: โ โ โ โ
โ pool = wl_shm_create_poolโ โ โ โ
โ (shm, fd, size) โโโโโโโ>โ Compositor maps โ โ
โ โ โ the same fd โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ Shared Memory โ โ
โ โ โ โ (Compositor's โ โ
โ โ โ โ View) - SAME โ โ
โ โ โ โ PHYSICAL MEM โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโ โ
โ 4. Create buffer: โ โ โ
โ buffer = pool.create_buf โ โ โ
โ โ โ โ
โ 5. Paint pixels: โ โ โ
โ for (i=0; i<n; i++) โ โ โ
โ pixels[i] = 0xFFRRGGBB โ โ โ
โ โ โ โ
โ 6. Attach and commit: โ โ โ
โ wl_surface_attach(buf) โโโโโโโ>โ 7. Compositor reads โ
โ wl_surface_commit() โ โ directly from โ
โ โ โ shared memory! โ
โ โ โ โ
โ โ โ NO COPY NEEDED โ
โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ
Key Insight: The file descriptor (fd) is passed over the Unix socket
using SCM_RIGHTS. Both processes map the same underlying memory.
The compositor never copies pixel dataโit reads directly from your buffer.
4. The Configure/Ack/Commit Cycle
One of the most confusing aspects of xdg-shell is the configure cycle:
XDG-SHELL CONFIGURE CYCLE
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Why This Exists:
- Compositor suggests window size/state
- Client must acknowledge before committing content
- Prevents race conditions (client draws at wrong size)
- Ensures synchronized state between compositor and client
The Flow:
Client Compositor
โ โ
โ xdg_surface.get_toplevel() โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ
โ wl_surface.commit() [initial] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ "Client wants a window"
โ โ
โ xdg_toplevel.configure(w,h,s) โ "Try this size/state"
โ<โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ xdg_surface.configure(serial) โ "Configuration complete"
โ<โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ xdg_surface.ack_configure(serial) โ "I understand"
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ
โ [Create buffer at suggested size] โ
โ [Paint content to buffer] โ
โ โ
โ wl_surface.attach(buffer) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ wl_surface.commit() โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ
โ โ Window appears on screen!
Critical Rules:
โ DO NOT commit content before receiving configure
โ DO NOT commit without acking first
โ DO NOT ignore the suggested size (you can, but it's rude)
โ DO ack even if you use a different size
โ DO handle multiple configures (only ack the last one)
Complete Project Specification
Functional Requirements
- Connection: Connect to the default Wayland display (
$WAYLAND_DISPLAYorwayland-0) - Protocol Negotiation: Bind to required globals:
wl_compositor,wl_shm,xdg_wm_base - Surface Creation: Create a
wl_surfacewith anxdg_toplevelrole - Buffer Management: Create and manage shared memory buffers
- Rendering: Fill the window with a solid color (configurable)
- Event Handling: Handle configure events and window resize
- Cleanup: Properly destroy all resources on exit
Non-Functional Requirements
- Must compile with only
-lwayland-client(plus generated protocol code) - Must not use any toolkit (GTK, Qt, SDL)
- Must handle Ctrl+C gracefully
- Must not leak memory or file descriptors
Expected Behavior
When running, the program should:
- Display diagnostic output showing protocol negotiation
- Show a solid-colored window that can be resized
- Respond to window close events
- Clean up properly on exit
Solution Architecture
High-Level Design
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ main() โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ 1. Initialize state structure โ โ
โ โ 2. Connect to Wayland display โ โ
โ โ 3. Get registry, bind globals โ โ
โ โ 4. Create surface hierarchy โ โ
โ โ 5. Enter event loop โ โ
โ โ 6. Cleanup on exit โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Registry โ โ Surface โ โ Buffer โ
โ Handling โ โ Management โ โ Management โ
โ โ โ โ โ โ
โ - Enumerate โ โ - Create โ โ - Create pool โ
โ globals โ โ wl_surface โ โ - Create buffer โ
โ - Bind to โ โ - Create โ โ - Paint pixels โ
โ interfaces โ โ xdg_surface โ โ - Attach/commit โ
โ - Handle โ โ - Handle โ โ - Handle resize โ
โ removal โ โ configure โ โ - Handle releaseโ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
State Structure Design
struct client_state {
// Display connection
struct wl_display *display;
struct wl_registry *registry;
// Bound globals
struct wl_compositor *compositor;
struct wl_shm *shm;
struct xdg_wm_base *xdg_wm_base;
// Surface objects
struct wl_surface *surface;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *xdg_toplevel;
// Buffer state
struct wl_buffer *buffer;
void *buffer_data;
size_t buffer_size;
// Window state
int32_t width;
int32_t height;
uint32_t last_configure_serial;
bool configured;
bool running;
// Appearance
uint32_t color; // XRGB format
};
Listener Architecture
Each Wayland object that sends events needs a listener:
LISTENER CALLBACKS
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
wl_registry_listener:
โโโ global() โ Called for each available global
โโโ global_remove() โ Called when global is removed
xdg_wm_base_listener:
โโโ ping() โ Compositor liveness check, must pong
xdg_surface_listener:
โโโ configure() โ Surface configuration with serial
xdg_toplevel_listener:
โโโ configure() โ Size/state suggestion
โโโ close() โ User requested close
wl_buffer_listener:
โโโ release() โ Buffer can be reused/destroyed
Phased Implementation Guide
Phase 1: Connection and Registry (Day 1)
Goal: Connect to Wayland and enumerate available globals
Steps:
- Create basic project structure with Makefile
- Include necessary headers
- Implement
wl_display_connect() - Get registry and add listener
- Print all available globals
Verification:
$ ./wayland_client
Connected to Wayland display
Global: wl_compositor v6
Global: wl_shm v1
Global: xdg_wm_base v5
...
Key Code Pattern:
static void registry_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface,
uint32_t version) {
printf("Global: %s v%u\n", interface, version);
if (strcmp(interface, wl_compositor_interface.name) == 0) {
state->compositor = wl_registry_bind(registry, name,
&wl_compositor_interface, 4);
}
// ... bind other globals
}
Phase 2: Surface Creation (Day 2-3)
Goal: Create a surface with xdg-shell role
Steps:
- Bind to
wl_compositor,wl_shm,xdg_wm_base - Create
wl_surfacefrom compositor - Implement xdg_wm_base ping handler
- Create
xdg_surfaceandxdg_toplevel - Set window title
- Initial commit to trigger configure
Verification:
$ WAYLAND_DEBUG=1 ./wayland_client 2>&1 | grep xdg
[...] xdg_wm_base@6.get_xdg_surface(...)
[...] xdg_surface@7.get_toplevel(...)
[...] -> xdg_toplevel@8.configure(800, 600, ...)
[...] -> xdg_surface@7.configure(1234)
Phase 3: Buffer Management (Day 3-4)
Goal: Create shared memory buffer and paint pixels
Steps:
- Implement
create_shm_file()usingmemfd_create() - Calculate buffer size (
width * height * 4) - Create
wl_shm_pool - Create
wl_bufferfrom pool - Paint solid color to buffer
- Implement buffer release handler
Key Functions:
static int create_shm_file(size_t size);
static struct wl_buffer* create_buffer(struct client_state *state);
static void paint_buffer(struct client_state *state, uint32_t color);
Phase 4: Configure Handling (Day 4-5)
Goal: Properly handle the configure/ack/commit cycle
Steps:
- Store configure serial
- Acknowledge configure
- Create buffer at configured size
- Paint and attach buffer
- Commit surface
Critical Pattern:
static void xdg_surface_configure(void *data,
struct xdg_surface *xdg_surface,
uint32_t serial) {
struct client_state *state = data;
xdg_surface_ack_configure(xdg_surface, serial);
if (state->buffer) {
wl_buffer_destroy(state->buffer);
}
state->buffer = create_buffer(state);
paint_buffer(state, state->color);
wl_surface_attach(state->surface, state->buffer, 0, 0);
wl_surface_damage_buffer(state->surface, 0, 0,
state->width, state->height);
wl_surface_commit(state->surface);
state->configured = true;
}
Phase 5: Event Loop and Cleanup (Day 5-6)
Goal: Main event loop and proper resource cleanup
Steps:
- Implement main event loop with
wl_display_dispatch() - Handle SIGINT for graceful shutdown
- Implement cleanup in reverse order of creation
- Verify no leaks with valgrind
Event Loop:
while (state.running && wl_display_dispatch(state.display) != -1) {
// Events are handled in callbacks
}
Phase 6: Resize Handling (Day 6-7)
Goal: Properly handle window resize
Steps:
- Handle
xdg_toplevel.configurewith new dimensions - Store new width/height
- Recreate buffer at new size on
xdg_surface.configure - Handle size (0, 0) meaning โclientโs choiceโ
Testing Strategy
Manual Testing Checklist
- Window appears on screen with correct color
- Window can be resized - color fills new area
- Window can be minimized and restored
- Window can be maximized
- Window can be closed with window controls
- Program exits cleanly on Ctrl+C
- No visual artifacts during resize
Automated Verification
# Check for memory leaks
valgrind --leak-check=full ./wayland_client
# Verify protocol correctness
WAYLAND_DEBUG=1 ./wayland_client 2>&1 | grep -E "(error|warning)"
# Test on different compositors
# - Sway
# - GNOME Wayland
# - weston
Expected Protocol Trace
A successful run should show this sequence in WAYLAND_DEBUG=1:
wl_display@1.get_registry(new id wl_registry@2)
wl_registry@2.bind(1, "wl_compositor", 4)
wl_registry@2.bind(3, "wl_shm", 1)
wl_registry@2.bind(6, "xdg_wm_base", 5)
wl_compositor@100.create_surface(new id wl_surface@200)
xdg_wm_base@102.get_xdg_surface(new id xdg_surface@300, wl_surface@200)
xdg_surface@300.get_toplevel(new id xdg_toplevel@400)
xdg_toplevel@400.set_title("My Wayland Client")
wl_surface@200.commit()
-> xdg_toplevel@400.configure(800, 600, array)
-> xdg_surface@300.configure(1234)
xdg_surface@300.ack_configure(1234)
wl_shm@101.create_pool(new id wl_shm_pool@500, fd 3, 1920000)
wl_shm_pool@500.create_buffer(new id wl_buffer@600, 0, 800, 600, 3200, 1)
wl_surface@200.attach(wl_buffer@600, 0, 0)
wl_surface@200.damage_buffer(0, 0, 800, 600)
wl_surface@200.commit()
Common Pitfalls and Debugging
Problem: Window doesnโt appear
Possible causes:
- Forgot to call
wl_surface_commit()after initial setup - Didnโt ack configure before committing content
- Buffer not attached before commit
- Running on X11 (check
echo $XDG_SESSION_TYPE)
Debug:
WAYLAND_DEBUG=1 ./wayland_client 2>&1 | grep -i error
Problem: Black window instead of colored
Possible causes:
- Painting after commit (too late)
- Wrong pixel format (using ARGB instead of XRGB)
- Buffer size mismatch with surface size
Debug:
// Add after painting
fprintf(stderr, "Buffer: %dx%d, first pixel = 0x%08x\n",
state->width, state->height,
((uint32_t*)state->buffer_data)[0]);
Problem: Crash on resize
Possible causes:
- Destroying buffer while still in use by compositor
- Not handling wl_buffer.release
- Memory corruption from size calculations
Fix: Use double buffering or wait for release event
Problem: Protocol error on startup
Common message: โinvalid objectโ
Cause: Using object before itโs created, or after itโs destroyed
Debug: Check object creation order matches protocol requirements
Extensions and Challenges
Challenge 1: Gradient Background
Instead of solid color, render a gradient from top to bottom.
Challenge 2: Double Buffering
Implement proper double buffering with two buffers.
Challenge 3: Animation
Animate the color over time using frame callbacks.
Challenge 4: Keyboard Input
Bind to wl_seat, get keyboard, handle key events to change color.
Challenge 5: Multi-Monitor
Enumerate wl_output globals and display on a specific monitor.
Real-World Connections
How This Relates to Real Applications
- GTK4: Uses similar code internally with
GdkWaylandDisplay - Qt:
QWaylandWindowimplements the same surface lifecycle - SDL2:
SDL_CreateWindowon Wayland does exactly this - Firefox: WebRender uses shared memory buffers like this
- Electron: Ozone layer implements Wayland using these primitives
Career Applications
- Graphics Driver Development: Understanding buffer sharing is essential
- Window Manager Development: This is the client side; compositor is the other
- Embedded Linux: Automotive/IVI systems use Wayland extensively
- Gaming: Game engines need to understand display server protocols
- Desktop Environment Development: GNOME, KDE rely on these concepts
Resources
Essential Reading
| Resource | Purpose |
|---|---|
| โThe Wayland Bookโ Ch. 1-4 | Protocol fundamentals |
| โThe Wayland Bookโ Ch. 8-9 | XDG-Shell specifics |
| โThe Linux Programming Interfaceโ Ch. 54 | Shared memory |
| โEffective Cโ Ch. 6 | Memory management patterns |
Code References
weston/clients/simple-shm.c- Canonical minimal clientwayland/tests/- Test clients from libwayland- GTK4 source:
gdk/wayland/- Production implementation
Online Documentation
- https://wayland-book.com - The Wayland Book (free online)
- https://wayland.app - Protocol documentation browser
/usr/share/wayland/wayland.xml- Core protocol definition/usr/share/wayland-protocols/- Extension protocols
Self-Assessment Checklist
Before considering this project complete, verify you can:
- Explain why Wayland exists and how it differs from X11
- Describe the role of wl_registry and global binding
- Explain the configure/ack/commit cycle without looking at notes
- Draw the buffer lifecycle state machine from memory
- Debug a protocol error using WAYLAND_DEBUG=1
- Modify the color without recompiling (via command line)
- Add keyboard input to your client
- Explain what happens when the user resizes the window
- Describe how shared memory enables zero-copy rendering
- Compare your code to a GTK4 simple window (how much is hidden?)
Completing this project gives you a solid foundation in Wayland client development. You now understand what every Wayland application does under the hood, even when hidden by toolkits. Next, consider Project 5 (X11 Comparison) while this knowledge is fresh, then progress to Project 2 (Compositor) to see the other side of the protocol.