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:

  1. Explain compositor architecture - Describe the three roles of a Wayland compositor: protocol server, rendering pipeline, and policy engine
  2. Implement server-side protocol handling - Create surfaces, route input events, manage object lifecycles
  3. Understand DRM/KMS - Explain how compositors control display hardware directly
  4. Manage scene graphs - Use wlroots scene API to position and render surfaces
  5. Route input correctly - Implement focus management and input event delivery
  6. 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

  1. Backend Initialization: Start DRM/KMS (or Wayland nested for development)
  2. Output Management: Handle monitor detection, mode setting, hotplug
  3. Protocol Implementation: Advertise wl_compositor, xdg_wm_base, wl_seat
  4. Surface Management: Create and track xdg_toplevel surfaces
  5. Rendering: Composite surfaces using wlr_scene
  6. Input Handling: Route keyboard and pointer events to focused surface
  7. Focus Management: Click-to-focus with visual indication
  8. Window Movement: Alt+drag to move windows
  9. 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:

  1. Create project with Makefile (link wlroots, wayland-server)
  2. Initialize wl_display, wlr_backend, wlr_renderer
  3. Create wlr_scene
  4. Handle new_output signal
  5. Create output frame handler
  6. 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:

  1. Create wlr_xdg_shell
  2. Handle new_xdg_surface signal
  3. Create my_view structure
  4. Add to scene graph with wlr_scene_xdg_surface_create
  5. 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:

  1. Create wlr_seat
  2. Create wlr_cursor
  3. Handle keyboard input device
  4. Handle pointer motion
  5. Implement focus on click
  6. 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:

  1. Implement Alt+drag to move
  2. Implement Alt+right-drag to resize
  3. Implement Alt+Tab focus cycling
  4. 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:

  1. Handle client crashes gracefully
  2. Implement proper focus indication (border)
  3. Support multiple monitors
  4. Add configuration (background color, keybinds)
  5. 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.