Project 2: Simple Wayland Compositor with wlroots
Project 2: Simple Wayland Compositor with wlroots
Project Overview
| Attribute | Value |
|---|---|
| Difficulty | Advanced/Master (Level 5) |
| Time Estimate | 1+ month |
| Programming Language | C |
| Knowledge Area | Window Managers / Graphics Pipeline |
| Main Book | โThe Wayland Bookโ by Drew DeVault |
| Coolness Level | Level 5: Pure Magic (Super Cool) |
| Business Potential | Open Core Infrastructure |
| Reference Implementation | tinywl (wlroots reference compositor) |
What youโll build: A minimal but functional Wayland compositor using the wlroots library that can display client windows, handle keyboard/mouse input, and manage window focus.
Why it teaches compositors: wlroots abstracts the hard parts (DRM/KMS, libinput) while exposing the compositor logic. Youโll implement window stacking, damage tracking, and the compositorโs view of the Wayland protocol.
Learning Objectives
By completing this project, you will be able to:
- Explain compositor architecture - Describe the three roles of a Wayland compositor: protocol server, rendering pipeline, and policy engine
- Implement server-side protocol handling - Create surfaces, route input events, manage object lifecycles
- Understand DRM/KMS - Explain how compositors control display hardware directly
- Manage scene graphs - Use wlroots scene API to position and render surfaces
- Route input correctly - Implement focus management and input event delivery
- Handle multiple outputs - Support multi-monitor configurations with independent rendering
The Core Question Youโre Answering
โWhat does it really mean to be a Wayland compositor, and how do the pieces fit together to create a functional windowing system?โ
This project forces you to answer fundamental questions about display servers:
-
What is a compositorโs job? To negotiate protocol objects (surfaces, seats, outputs), route input events to the right clients, composite client buffers onto the screen, and maintain the windowing policy (focus, stacking, positioning)
-
How does the protocol become reality? By implementing the server sideโwhen a client calls
wl_compositor.create_surface(), your code allocates a surface object and sends back a resource ID -
Where does rendering actually happen? Clients render to shared buffers (DMA-BUF or shared memory), your compositor just positions and blends them onto outputs
-
How do you manage state? The compositor is statefulโit tracks which windows exist, where they are, which has focus, what input devices are connected
-
What makes Wayland secure? Input isolationโonly the focused window receives keyboard input, other windows canโt snoop
By building a compositor, youโll discover:
-
Why wlroots exists: Direct DRM/KMS and libinput are complexโwlroots provides scene graphs, output management, and input handling so you focus on policy (how windows behave) not mechanism (how pixels appear)
-
Why Wayland is stateless from the clientโs perspective: Clients render independently, the compositor just tells them when to drawโthis enables security and smooth animation
-
Why the protocol is extensible: Base protocol (wl_compositor, wl_surface) is minimal; extensions (xdg-shell for windows, layer-shell for panels) add features without breaking compatibility
-
Why thereโs no โWayland window managerโ: The compositor IS the window managerโyou decide focus behavior, tiling vs floating, keybindings, everything
The deeper insight: A compositor is simultaneously a protocol server, a rendering pipeline, and a policy engine. Itโs the Unix philosophy invertedโinstead of small tools composed together, itโs one tool doing everything related to display.
Deep Theoretical Foundation
1. Compositor Architecture
A Wayland compositor has three distinct responsibilities:
COMPOSITOR RESPONSIBILITIES
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ WAYLAND COMPOSITOR โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ PROTOCOL SERVER โ โ RENDERING โ โ POLICY ENGINE โ โ
โ โ โ โ PIPELINE โ โ โ โ
โ โ โข Accept client โ โ โ โ โข Focus management โ โ
โ โ connections โ โ โข Collect โ โ โข Window placement โ โ
โ โ โข Implement โ โ buffers โ โ โข Keybindings โ โ
โ โ wl_compositor โ โ โข Compose scene โ โ โข Tiling/floating โ โ
โ โ โข Create โ โ โข Send to GPU โ โ โข Workspaces โ โ
โ โ surfaces โ โ โข Output to โ โ โข Decorations โ โ
โ โ โข Route events โ โ display โ โ โ โ
โ โ โข Manage โ โ โ โ โ โ
โ โ lifecycles โ โ โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโดโโโโโโโโโโโโ
โผ โผ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ wlroots handles โ โ You implement โ
โ mechanism: โ โ policy: โ
โ โข DRM/KMS โ โ โข What happens โ
โ โข libinput โ โ on click โ
โ โข Scene graph โ โ โข Where windows โ
โ โข Buffer mgmt โ โ appear โ
โ โ โ โข Keyboard โ
โ โ โ shortcuts โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
2. The Rendering Pipeline
Understanding how frames are rendered is essential:
COMPOSITOR RENDERING PIPELINE (Frame-by-Frame)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
PHASE 1: CLIENT RENDERING (Parallel, Independent)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ Client A โ โ Client B โ โ Client C โ
โ (Terminal) โ โ (Browser) โ โ (Editor) โ
โ โ โ โ โ โ
โ Render to โ โ Render to โ โ Render to โ
โ wl_buffer โ โ wl_buffer โ โ wl_buffer โ
โ โ โ โ โ โ
โ attach() โ โ attach() โ โ attach() โ
โ commit() โ โ commit() โ โ commit() โ
โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโ
โ โ โ
โโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโ
โผ
PHASE 2: COMPOSITOR RECEIVES BUFFERS
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Wayland Compositor โ
โ โ
โ wl_surface A โ buffer A โ
โ wl_surface B โ buffer B โ
โ wl_surface C โ buffer C โ
โ โ
โ Each surface has: โ
โ - Position (x, y) โ
โ - Z-order (stacking) โ
โ - Damage region (what changed) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
PHASE 3: SCENE GRAPH CONSTRUCTION (wlroots)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ wlr_scene (Root) โ
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ
โ Background โ โ Window Layer โ โ Overlay Layer โ
โ (Wallpaper) โ โ (Apps) โ โ (Panels) โ
โโโโโโโโโโโโโโโโโ โโโโโโโโโฌโโโโโโโโ โโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโผโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ
โSurface A โ โSurface B โ โSurface C โ
โ Z=100 โ โ Z=101 โ โ Z=102 โ
โ(10, 10) โ โ(400,200) โ โ(50, 500) โ
โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ
โ
โผ
PHASE 4: DAMAGE TRACKING & OPTIMIZATION
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Compositor analyzes what changed:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Damage Regions: โ
โ โ
โ โข Region 1: Terminal moved (10,10,100,50) โ
โ โข Region 2: Browser scrolled (400,200,800,600)โ
โ โข Editor unchanged โ NO damage, skip โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
PHASE 5: GPU COMPOSITING (OpenGL/Vulkan)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GPU Render Pass: โ
โ โ
โ 1. Clear framebuffer (or use previous frame) โ
โ โ
โ 2. For each damaged region: โ
โ for each surface (back to front): โ
โ โ
โ if DMA-BUF: Zero-copy texture bind โ
โ if SHM: Upload pixels to GPU texture โ
โ โ
โ glDrawArrays(quad at surface position) โ
โ โ
โ 3. Render cursor on top โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
PHASE 6: DISPLAY OUTPUT (DRM/KMS Atomic Commit)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DRM Atomic Commit: โ
โ โ
โ ioctl(drm_fd, DRM_IOCTL_MODE_ATOMIC, ...) โ
โ โ
โ Kernel schedules buffer swap at VBLANK โ
โ (No tearing!) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโ
โ Physical Screen โ
โ โ
โ Pixels visible! โ
โโโโโโโโโโโโโโโโโโโ
3. DRM/KMS Subsystem
Your compositor must control the display hardware:
DRM/KMS ARCHITECTURE
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
User Space (Your Compositor)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ Open /dev/dri/card0
โ
โ ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, ...)
โ ioctl(fd, DRM_IOCTL_MODE_ATOMIC, ...)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Kernel Space (DRM Subsystem)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DRM Core โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Mode Setting (KMS) โ โ
โ โ โ โ
โ โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโโ โ โ
โ โ โ CRTC โโโโโถโ Encoder โโโโโถโConnector โ โ โ
โ โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโโ โ โ
โ โ โ โ โ โ โ
โ โ Scanout engine Signal format Physical port โ โ
โ โ (reads buffer) (HDMI/DP/VGA) (HDMI-A-1) โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Framebuffer โ โ โ
โ โ โ - Points to GEM buffer object โ โ โ
โ โ โ - Width, height, stride, format โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ โ Atomic Mode Setting: โ โ
โ โ - Set all properties in one ioctl โ โ
โ โ - Either all changes apply or none โ โ
โ โ - VBLANK synchronization โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ GEM (Graphics Execution Manager) โ โ
โ โ โ โ
โ โ Buffer objects for GPU memory allocation โ โ
โ โ DMA-BUF for cross-process buffer sharing โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Hardware
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GPU โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Display โ โ Display โ โ Video Memory (VRAM) โ โ
โ โ Engine 1 โ โ Engine 2 โ โ โ โ
โ โ (CRTC) โ โ (CRTC) โ โ Framebuffers stored hereโ โ
โ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โผ โผ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ
โ โ HDMI Port โ โ DisplayPort โ โ
โ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โ
โโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโ โโโโโโโโโโโ
โ Monitor โ โ Monitor โ
โ 1 โ โ 2 โ
โโโโโโโโโโโ โโโโโโโโโโโ
4. wlroots Architecture
wlroots provides building blocks for compositors:
WLROOTS COMPONENT ARCHITECTURE
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your Compositor Code โ
โ โ
โ main() โ Setup wlroots โ Connect signals โ Event loop โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
Uses wlroots components:
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ wlroots Library โ
โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ wlr_backend โ โ wlr_renderer โ โ wlr_scene โโ
โ โ โ โ โ โ โโ
โ โ โข DRM backend โ โ โข OpenGL โ โ โข Scene graph โโ
โ โ โข Wayland โ โ โข Vulkan โ โ โข Damage tracking โโ
โ โ nested โ โ โข Pixman (SW) โ โ โข Surface positioning โโ
โ โ โข X11 nested โ โ โ โ โข Z-ordering โโ
โ โ โข Headless โ โ โ โ โข Automatic output โโ
โ โ โ โ โ โ rendering โโ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ wlr_output โ โ wlr_seat โ โ wlr_xdg_shell โโ
โ โ โ โ โ โ โโ
โ โ โข Output layout โ โ โข Keyboard โ โ โข xdg_surface โโ
โ โ โข Mode setting โ โ โข Pointer โ โ โข xdg_toplevel โโ
โ โ โข Frame โ โ โข Touch โ โ โข xdg_popup โโ
โ โ scheduling โ โ โข Input routing โ โ โข Configure events โโ
โ โ โข Hotplug โ โ โข Focus mgmt โ โ โโ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โwlr_layer_shell โ โ wlr_cursor โ โ wlr_compositor โโ
โ โ โ โ โ โ โโ
โ โ โข Panel support โ โ โข Cursor image โ โ โข wl_compositor impl โโ
โ โ โข Overlays โ โ โข Movement โ โ โข wl_surface creation โโ
โ โ โข Backgrounds โ โ โข Hardware โ โ โข wl_subcompositor โโ
โ โ โข Lock screens โ โ cursor โ โ โโ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Complete Project Specification
Functional Requirements
- Backend Initialization: Start DRM/KMS (or Wayland nested for development)
- Output Management: Handle monitor detection, mode setting, hotplug
- Protocol Implementation: Advertise wl_compositor, xdg_wm_base, wl_seat
- Surface Management: Create and track xdg_toplevel surfaces
- Rendering: Composite surfaces using wlr_scene
- Input Handling: Route keyboard and pointer events to focused surface
- Focus Management: Click-to-focus with visual indication
- Window Movement: Alt+drag to move windows
- Exit Handling: Ctrl+Shift+Escape to quit
Non-Functional Requirements
- Must run from a TTY (DRM backend) or nested (Wayland/X11 backend)
- Must handle client crashes gracefully
- Must not leak resources when clients disconnect
- Should maintain 60 FPS under normal conditions
Solution Architecture
High-Level Component Diagram
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ my_compositor โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ server.c โ โ
โ โ โ โ
โ โ struct my_server { โ โ
โ โ struct wl_display *display; โ โ
โ โ struct wlr_backend *backend; โ โ
โ โ struct wlr_renderer *renderer; โ โ
โ โ struct wlr_scene *scene; โ โ
โ โ struct wlr_scene_output_layout *scene_layout; โ โ
โ โ โ โ
โ โ struct wlr_xdg_shell *xdg_shell; โ โ
โ โ struct wl_list views; // my_view list โ โ
โ โ โ โ
โ โ struct wlr_cursor *cursor; โ โ
โ โ struct wlr_seat *seat; โ โ
โ โ struct my_view *focused_view; โ โ
โ โ โ โ
โ โ struct wl_listener new_output; โ โ
โ โ struct wl_listener new_xdg_surface; โ โ
โ โ struct wl_listener cursor_motion; โ โ
โ โ struct wl_listener cursor_button; โ โ
โ โ struct wl_listener keyboard_key; โ โ
โ โ }; โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ output.c โ โ view.c โ โ input.c โ โ
โ โ โ โ โ โ โ โ
โ โ handle_new_ โ โ handle_new_ โ โ handle_cursor_ โ โ
โ โ output() โ โ xdg_surface() โ โ motion() โ โ
โ โ handle_output_ โ โ handle_map() โ โ handle_cursor_ โ โ
โ โ frame() โ โ handle_unmap() โ โ button() โ โ
โ โ handle_output_ โ โ handle_destroy()โ โ handle_keyboard_ โ โ
โ โ destroy() โ โ focus_view() โ โ key() โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
View Structure (Window Representation)
struct my_view {
struct wl_list link; // server.views
struct my_server *server;
struct wlr_xdg_toplevel *xdg_toplevel;
struct wlr_scene_tree *scene_tree;
// Listeners
struct wl_listener map;
struct wl_listener unmap;
struct wl_listener destroy;
struct wl_listener request_move;
struct wl_listener request_resize;
// State
int x, y;
bool mapped;
};
Signal/Event Flow
CLIENT CREATES WINDOW
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
1. Client: xdg_wm_base.get_xdg_surface()
โโโ wlr_xdg_shell emits: events.new_surface
2. Your handler: handle_new_xdg_surface()
โโโ Allocate my_view
โโโ Create scene tree: wlr_scene_xdg_surface_create()
โโโ Connect listeners: map, unmap, destroy
โโโ Add to server.views list
3. Client: commits surface content
โโโ xdg_surface emits: events.map
4. Your handler: handle_view_map()
โโโ Set initial position
โโโ Focus the view
โโโ view.mapped = true
5. On each frame:
โโโ wlr_scene renders all mapped views
CLIENT CLOSES WINDOW
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
1. Client: xdg_toplevel.destroy() (or client disconnects)
โโโ xdg_surface emits: events.unmap
2. Your handler: handle_view_unmap()
โโโ view.mapped = false
โโโ If focused, focus another view
3. xdg_surface emits: events.destroy
4. Your handler: handle_view_destroy()
โโโ Remove from server.views
โโโ Disconnect listeners
โโโ Free my_view
Phased Implementation Guide
Phase 1: Minimal Compositor (Week 1)
Goal: Display a background color, handle output lifecycle
Steps:
- Create project with Makefile (link wlroots, wayland-server)
- Initialize wl_display, wlr_backend, wlr_renderer
- Create wlr_scene
- Handle new_output signal
- Create output frame handler
- Add socket and run event loop
Verification:
# From TTY (no existing Wayland/X11 session)
$ ./my_compositor
# Should show solid gray background
# Ctrl+C to exit
Key Code:
int main(void) {
struct wl_display *display = wl_display_create();
struct wlr_backend *backend = wlr_backend_autocreate(display, NULL);
struct wlr_renderer *renderer = wlr_renderer_autocreate(backend);
wlr_renderer_init_wl_display(renderer, display);
struct wlr_scene *scene = wlr_scene_create();
struct wlr_output_layout *output_layout = wlr_output_layout_create(display);
wlr_scene_attach_output_layout(scene, output_layout);
// Handle new outputs
server.new_output.notify = handle_new_output;
wl_signal_add(&backend->events.new_output, &server.new_output);
const char *socket = wl_display_add_socket_auto(display);
wlr_backend_start(backend);
wl_display_run(display);
return 0;
}
Phase 2: XDG Shell Support (Week 2)
Goal: Accept client windows, display them
Steps:
- Create wlr_xdg_shell
- Handle new_xdg_surface signal
- Create my_view structure
- Add to scene graph with wlr_scene_xdg_surface_create
- Handle map/unmap events
Verification:
$ ./my_compositor &
$ WAYLAND_DISPLAY=wayland-1 weston-terminal
# Terminal window should appear
Phase 3: Input Handling (Week 2-3)
Goal: Route keyboard and pointer events
Steps:
- Create wlr_seat
- Create wlr_cursor
- Handle keyboard input device
- Handle pointer motion
- Implement focus on click
- Send events to focused view
Key Concept: Input routing
void handle_cursor_button(struct wl_listener *listener, void *data) {
struct wlr_pointer_button_event *event = data;
if (event->state == WLR_BUTTON_PRESSED) {
// Find view under cursor
struct my_view *view = view_at(server, cursor->x, cursor->y);
if (view) {
focus_view(view);
}
}
// Forward to focused view
wlr_seat_pointer_notify_button(seat, event->time_msec,
event->button, event->state);
}
Phase 4: Window Management (Week 3-4)
Goal: Move, resize, focus windows
Steps:
- Implement Alt+drag to move
- Implement Alt+right-drag to resize
- Implement Alt+Tab focus cycling
- Add exit keybinding (Ctrl+Shift+Escape)
Grab Mode State Machine:
GRAB_NONE โโ[Alt+Click]โโโบ GRAB_MOVE
โ โ
โ [Release]
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
GRAB_NONE โโ[Alt+RightClick]โโโบ GRAB_RESIZE
โ โ
โ [Release]
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Phase 5: Polish (Week 4+)
Goal: Production-quality behavior
Steps:
- Handle client crashes gracefully
- Implement proper focus indication (border)
- Support multiple monitors
- Add configuration (background color, keybinds)
- Proper cleanup on exit
Testing Strategy
Manual Testing Matrix
| Test Case | Expected Result |
|---|---|
| Launch with no clients | Background displayed |
| Launch weston-terminal | Window appears, receives input |
| Click different windows | Focus changes correctly |
| Alt+drag window | Window moves with cursor |
| Close window | Window disappears, no crash |
| Kill client process | Compositor survives |
| Connect second monitor | Both outputs work |
| Disconnect monitor | Graceful handling |
| Ctrl+Shift+Esc | Compositor exits cleanly |
Debugging Techniques
# Enable wlroots debug output
WLR_DEBUG=1 ./my_compositor
# Use software renderer (if GPU issues)
WLR_RENDERER=pixman ./my_compositor
# Run nested in existing session
WLR_BACKENDS=wayland ./my_compositor
# or
WLR_BACKENDS=x11 ./my_compositor
# Client-side protocol debug
WAYLAND_DEBUG=1 weston-terminal
Common Pitfalls and Debugging
Problem: Compositor crashes on client connect
Cause: Not handling xdg_surface roles correctly
Fix: Check if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) return;
Problem: No cursor visible
Cause: Didnโt set up wlr_cursor or load cursor theme Fix:
struct wlr_xcursor_manager *cursor_mgr =
wlr_xcursor_manager_create(NULL, 24);
wlr_xcursor_manager_load(cursor_mgr, 1);
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
Problem: Input not reaching clients
Cause: Not calling wlr_seat keyboard/pointer enter Fix: When focusing, send enter events:
wlr_seat_keyboard_notify_enter(seat, surface,
keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers);
wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
Problem: Windows donโt appear
Cause: Scene tree not connected to output layout
Fix: Ensure wlr_scene_attach_output_layout() is called
Extensions and Challenges
Challenge 1: Window Decorations
Add server-side decorations (title bar, close button).
Challenge 2: Tiling Layout
Implement automatic tiling like i3/Sway.
Challenge 3: Workspaces
Add virtual desktop support with workspace switching.
Challenge 4: Layer Shell Support
Add wlr_layer_shell for panels (integrate with P04).
Challenge 5: Screenshot Protocol
Implement screencopy protocol for screenshots.
Real-World Connections
Production Compositors Using wlroots
- Sway: Full-featured tiling compositor, ~30k lines
- Wayfire: 3D effects compositor
- Phoc: Mobile compositor for Phosh
- cage: Kiosk compositor (single app)
- hikari: Stacking compositor inspired by CWM
Career Applications
- Linux Desktop Development: GNOME/KDE hire compositor developers
- Embedded Systems: Automotive, industrial displays use custom compositors
- Gaming Platforms: Steam Deck uses Gamescope (wlroots-based)
- VR/AR: Display composition is fundamental
- Cloud Gaming: Remote display requires compositor knowledge
Resources
Essential Reading (Before Starting)
| Resource | Chapter/Section |
|---|---|
| โThe Wayland Bookโ | Ch. 1-4 (client), Ch. 7-8 (server) |
| wlroots docs | Backend, renderer, scene API |
| tinywl source | Line-by-line study |
| Linux DRM docs | Mode Setting (KMS) |
Reference Implementations
| Source | Purpose |
|---|---|
| tinywl (~600 lines) | Minimal reference |
| Sway | Production quality |
| cage | Simple single-app |
Online Resources
- Drew DeVaultโs blog: โWriting a Wayland Compositorโ series
- https://gitlab.freedesktop.org/wlroots/wlroots
- freedesktop.org protocol specs
Self-Assessment Checklist
Before considering this project complete, verify you can:
- Explain what happens when a client creates an xdg_toplevel
- Draw the rendering pipeline from client buffer to screen
- Describe how input events reach the correct client
- Explain DRM/KMS at a high level (CRTC, encoder, connector)
- Modify your compositor to use a different background
- Add a new keybinding for any action
- Handle a new client connecting while running
- Debug why a clientโs window doesnโt appear
- Support multiple monitors with different resolutions
- Gracefully handle client crashes
Completing this project gives you deep understanding of how Linux desktops actually work. Youโve implemented the core of what GNOME, KDE, and Sway provide. You can now understand any compositorโs source code and contribute to Linux desktop development.