Project 5: X11 Comparison Project - Bare-Metal X11 Client
Project 5: X11 Comparison Project - Bare-Metal X11 Client
Project Overview
| Attribute | Value |
|---|---|
| Difficulty | Intermediate (Level 2) |
| Time Estimate | 1 week |
| Programming Language | C (also: C++, Rust, Zig) |
| Knowledge Area | Graphics, Windowing Systems |
| Main Book | Xlib Programming Manual (OโReilly) |
| Coolness Level | Level 3: Genuinely Clever |
| Business Potential | Resume Gold |
What youโll build: The exact same colored window as Project 1, but using raw Xlib to viscerally feel the differences between X11 and Wayland.
Why it teaches X11 vs Wayland: Nothing teaches the difference better than implementing the same thing in both. Youโll feel X11โs complexity (window properties, atoms, event masks) vs Waylandโs simplicity (but also Waylandโs โyou do the workโ philosophy).
Learning Objectives
By completing this project, you will be able to:
- Contrast X11 and Wayland architectures - Explain the fundamental differences in design philosophy
- Implement X11 client basics - Connect to display, create window, handle events
- Understand X11 atoms and properties - Use the property system for window metadata
- Handle Expose events - Implement the X11 redraw model
- Work with ICCCM/EWMH - Use window manager hints correctly
- Appreciate Waylandโs design choices - Understand why Wayland was created
The Core Question Youโre Answering
โWhat was so broken about X11 that the entire Linux graphics stack was redesigned from scratch, and what does the difference feel like in code?โ
By implementing the same window in both X11 and Wayland, youโll experience:
- X11โs server-side rendering model: You tell the server โdraw a rectangle hereโ
- Waylandโs client-side rendering: You render pixels yourself and share the buffer
- X11โs complexity: Atoms, properties, event masks, window hints
- Waylandโs minimalism: Just surfaces, buffers, and protocols
- X11โs security model: Any client can read any window
- Waylandโs isolation: Clients canโt see each other
The side-by-side comparison will cement your understanding of both systems and explain why Wayland exists.
Deep Theoretical Foundation
1. X11 vs Wayland Architecture Comparison
X11 ARCHITECTURE
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Client A Client B Client C
โ โ โ
โ X11 Protocol โ โ
โ (Network) โ โ
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโผโโโโโโโโโโโ
โ X Server โ
โ โ
โ โข Resource mgmt โ
โ โข Draw execution โ
โ โข Event routing โ
โ โข Window storage โ โ Server stores window contents!
โ โข Input handling โ
โโโโโโโโโโโโฌโโโโโโโโโโโ
โ
โโโโโโโโโโโโผโโโโโโโโโโโ
โ Compositor โ โ Separate process
โ (Compiz, Picom) โ
โโโโโโโโโโโโฌโโโโโโโโโโโ
โ
โโโโโโโโโโโโผโโโโโโโโโโโ
โ GPU โ
โโโโโโโโโโโโโโโโโโโโโโโ
WAYLAND ARCHITECTURE
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Client A Client B Client C
โ โ โ
โ Each client โ โ
โ renders own โ โ
โ content! โ โ
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโ
โ Buffer FDs
โโโโโโโโโโโโผโโโโโโโโโโโ
โ Wayland Compositor โ
โ (Display Server + โ
โ Window Manager + โ
โ Compositor) โ โ All-in-one!
โโโโโโโโโโโโฌโโโโโโโโโโโ
โ
โโโโโโโโโโโโผโโโโโโโโโโโ
โ GPU โ
โโโโโโโโโโโโโโโโโโโโโโโ
KEY DIFFERENCE: Who renders?
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
X11:
Client: "X server, please draw a red rectangle at (10, 10) size (100, 50)"
Server: *draws rectangle* *stores in window pixmap*
Result: Server has all the window contents in memory
Wayland:
Client: *creates buffer* *paints red rectangle* "Compositor, here's my buffer FD"
Compositor: *composites buffer to screen* *doesn't copy or store*
Result: Client owns its pixels, compositor just displays them

2. X11 Concepts You Must Understand
X11 CORE CONCEPTS
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
DISPLAY
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Connection to X server (local Unix socket or network)
- Format: hostname:display.screen (e.g., ":0.0")
- Multiple screens possible (rare today)
- $DISPLAY environment variable
WINDOW
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Rectangular area on screen
- Has parent (tree structure, root at top)
- Has attributes (size, position, border, background)
- Can be InputOutput (visible) or InputOnly (invisible grab)
PIXMAP
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Off-screen drawable
- Same format as window
- Used for double buffering, icons
GRAPHICS CONTEXT (GC)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Holds drawing parameters (color, font, line style)
- Reused across draw operations for efficiency
- Created per-connection
ATOMS
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Interned strings โ integer IDs
- Used for property names, selections, etc.
- Standard atoms: WM_NAME, WM_CLASS, _NET_WM_STATE
- Created with XInternAtom()
PROPERTIES
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Key-value storage on windows
- Key = atom, Value = typed data
- Window manager reads properties to know what to do
- XChangeProperty(), XGetWindowProperty()
EVENT MASKS
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Each window can receive different events
- Set with XSelectInput()
- ExposureMask, KeyPressMask, ButtonPressMask, StructureNotifyMask
3. The Expose Event Model
X11 uses an โExposeโ event model for repainting:
X11 EXPOSE EVENT FLOW
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Initial window creation:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
1. Client creates window: XCreateWindow()
2. Client maps window: XMapWindow()
3. Window appears, possibly obscured
4. X server notices window is now visible
5. X server sends Expose event to client
6. Client receives Expose, draws content
Window uncovered:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
1. User moves overlapping window away
2. X server notices previously obscured region now visible
3. X server sends Expose event(s) for damaged regions
4. Client must redraw exposed regions
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ โโโโโโโโโโโโโโโโ โ
โ โ Window B โ โ
โ โ (on top) โโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ User moves B โโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโ โ
โ โ Window A โ โ Expose event! โ
โ โ (needs โ Regions that โ
โ โ redraw) โ were covered โ
โ โโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Contrast with Wayland:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Wayland: Client always owns its buffer, no Expose events
- Wayland: Compositor has the pixels, just re-composites
- Wayland: Client only redraws when it wants to change content

4. Window Manager Hints (ICCCM/EWMH)
X11 uses properties for window manager communication:
WINDOW MANAGER COMMUNICATION
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
ICCCM (Inter-Client Communication Conventions Manual):
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
WM_NAME - Window title (Latin-1 encoded)
WM_CLASS - Instance and class names (for window matching)
WM_NORMAL_HINTS - Size hints (min, max, aspect ratio)
WM_HINTS - Urgency, icon, input focus model
WM_PROTOCOLS - Supported protocols (WM_DELETE_WINDOW)
WM_STATE - Window state (Normal, Iconic, Withdrawn)
EWMH (Extended Window Manager Hints):
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
_NET_WM_NAME - UTF-8 window title
_NET_WM_STATE - Fullscreen, maximized, sticky, etc.
_NET_WM_WINDOW_TYPE - Normal, dialog, dock, desktop, etc.
_NET_WM_PID - Process ID of client
_NET_WM_DESKTOP - Which desktop/workspace
_NET_ACTIVE_WINDOW - The currently focused window
Setting properties in X11:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Atom wm_name = XInternAtom(display, "WM_NAME", False);
XChangeProperty(display, window, wm_name, XA_STRING, 8,
PropModeReplace, (unsigned char*)"My Window", 9);
// For UTF-8 title (EWMH):
Atom net_wm_name = XInternAtom(display, "_NET_WM_NAME", False);
Atom utf8 = XInternAtom(display, "UTF8_STRING", False);
XChangeProperty(display, window, net_wm_name, utf8, 8,
PropModeReplace, (unsigned char*)"My Window", 9);
Contrast with Wayland:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
xdg_toplevel_set_title(toplevel, "My Window");
// One function call, UTF-8 by default, no atoms
Complete Project Specification
Functional Requirements
- Display Connection: Connect to $DISPLAY (or default)
- Window Creation: Create a top-level window
- Event Handling: Handle Expose, ConfigureNotify, ClientMessage
- Rendering: Fill window with solid color
- WM Integration: Set WM_NAME, handle WM_DELETE_WINDOW
- Resize Handling: Respond to window resize
Expected Behavior
Same as Project 1:
- Window appears with solid color
- Can be resized, moved, minimized
- Closes properly when user closes window
- Handles resize by repainting
Solution Architecture
Minimal X11 Client Structure
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct x11_state {
Display *display;
int screen;
Window root;
Window window;
GC gc;
Atom wm_delete_window;
int width, height;
unsigned long color;
int running;
};
Event Loop Pattern
void run_event_loop(struct x11_state *state) {
XEvent event;
while (state->running) {
XNextEvent(state->display, &event);
switch (event.type) {
case Expose:
if (event.xexpose.count == 0) {
// Last expose in series, redraw
redraw(state);
}
break;
case ConfigureNotify:
if (event.xconfigure.width != state->width ||
event.xconfigure.height != state->height) {
state->width = event.xconfigure.width;
state->height = event.xconfigure.height;
// Resize triggers Expose, so redraw happens
}
break;
case ClientMessage:
if ((Atom)event.xclient.data.l[0] == state->wm_delete_window) {
state->running = 0;
}
break;
}
}
}
Phased Implementation Guide
Phase 1: Connect and Create Window (Day 1-2)
Goal: Open display, create window, see it appear
Steps:
- XOpenDisplay(NULL)
- Get screen and root window
- XCreateSimpleWindow() with background color
- XSelectInput() for events
- XMapWindow()
- XFlush() and verify window appears
Code:
Display *display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "Cannot open display\n");
exit(1);
}
int screen = DefaultScreen(display);
Window root = RootWindow(display, screen);
Window window = XCreateSimpleWindow(
display, root,
100, 100, // x, y
800, 600, // width, height
1, // border width
BlackPixel(display, screen), // border
0xFF0000 // background (red)
);
XSelectInput(display, window,
ExposureMask | StructureNotifyMask | KeyPressMask);
XMapWindow(display, window);
XFlush(display);
Phase 2: Window Manager Integration (Day 2-3)
Goal: Set window title, handle close button
Steps:
- Set WM_NAME property
- Set _NET_WM_NAME for UTF-8
- Register for WM_DELETE_WINDOW protocol
- Handle ClientMessage event
Code:
// Set title
XStoreName(display, window, "My X11 Client");
// Handle close button
Atom wm_protocols = XInternAtom(display, "WM_PROTOCOLS", False);
Atom wm_delete = XInternAtom(display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, window, &wm_delete, 1);
// In event loop:
if (event.xclient.message_type == wm_protocols &&
(Atom)event.xclient.data.l[0] == wm_delete) {
running = 0;
}
Phase 3: Drawing with GC (Day 3-4)
Goal: Draw solid color in Expose handler
Steps:
- Create Graphics Context
- Set foreground color
- Handle Expose event
- XFillRectangle() to draw
Code:
GC gc = XCreateGC(display, window, 0, NULL);
XSetForeground(display, gc, 0xFF0000); // Red
// In Expose handler:
XFillRectangle(display, window, gc, 0, 0, width, height);
Phase 4: Resize Handling (Day 4-5)
Goal: Redraw on resize
Steps:
- Handle ConfigureNotify event
- Store new dimensions
- Redraw (Expose will follow)
Phase 5: Comparison Analysis (Day 5-7)
Goal: Document differences from Wayland
Exercise: Create a comparison table of:
- Lines of code
- Concepts required
- Error handling
- Security implications
- Debugging difficulty
Side-by-Side Code Comparison
Window Creation
X11 Wayland
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Display *d = XOpenDisplay(NULL); struct wl_display *d =
wl_display_connect(NULL);
int s = DefaultScreen(d); struct wl_registry *r =
Window root = RootWindow(d, s); wl_display_get_registry(d);
// Bind to wl_compositor
Window w = XCreateSimpleWindow( struct wl_surface *s =
d, root, 0, 0, 800, 600, wl_compositor_create_surface(c);
0, 0, 0xFF0000); struct xdg_surface *xs =
xdg_wm_base_get_xdg_surface(b, s);
struct xdg_toplevel *t =
xdg_surface_get_toplevel(xs);
XMapWindow(d, w); wl_surface_commit(s);
// Wait for configure, create buffer,
// paint pixels, attach, commit
Setting Window Title
X11 Wayland
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// ICCCM (Latin-1) xdg_toplevel_set_title(t, "Title");
XStoreName(d, w, "Title"); // That's it!
// EWMH (UTF-8)
Atom a = XInternAtom(d, "_NET_WM_NAME", False);
Atom utf8 = XInternAtom(d, "UTF8_STRING", False);
XChangeProperty(d, w, a, utf8, 8,
PropModeReplace, "Title", 5);
Drawing
X11 Wayland
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Server-side drawing // Client-side rendering
GC gc = XCreateGC(d, w, 0, NULL); int fd = memfd_create("buf", 0);
XSetForeground(d, gc, 0xFF0000); ftruncate(fd, size);
XFillRectangle(d, w, gc, 0, 0, void *data = mmap(...);
width, height);
// Paint pixels yourself
uint32_t *px = data;
for (int i = 0; i < w*h; i++)
px[i] = 0xFFFF0000;
// Create buffer, attach, commit
wl_surface_attach(s, buf, 0, 0);
wl_surface_commit(s);
Handling Resize
X11 Wayland
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Receive ConfigureNotify event // Receive xdg_toplevel.configure
case ConfigureNotify: static void handle_configure(
width = e.xconfigure.width; void *data, ..., int w, int h) {
height = e.xconfigure.height; state->width = w;
// Expose will follow, state->height = h;
// redraw there // Create new buffer at new size
break; // Paint, attach, ack, commit
}
case Expose:
if (e.xexpose.count == 0)
XFillRectangle(...);
Key Differences Summary
ARCHITECTURAL DIFFERENCES
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Aspect | X11 | Wayland
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Rendering | Server-side | Client-side
Buffer ownership | Server stores | Client stores
Redraw model | Expose events | Client-initiated
Text encoding | Latin-1 + atoms | UTF-8 everywhere
WM communication | Properties + atoms | Protocol requests
Security | Any client can snoop | Isolated by design
Complexity | Many concepts | Fewer, cleaner
Network support | Built-in | Not designed for it
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
CODE COMPARISON
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Metric | X11 | Wayland
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Lines of code | ~100 | ~300
Concepts needed | 8-10 | 5-6
Headers included | 3 | 2 + generated
Library calls | ~15 | ~20
Error points | Many | Clearer
Debugging | xev, xprop, xwininfo | WAYLAND_DEBUG=1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Testing Strategy
Functional Testing
| Test | X11 | Wayland |
|---|---|---|
| Window appears | Yes | Yes |
| Correct color | Yes | Yes |
| Resize works | Yes | Yes |
| Title shows | Yes | Yes |
| Close button works | Yes | Yes |
Environment Testing
# X11
$ ./x11_client
# Should work on X11 session
# Test XWayland
$ DISPLAY=:0 ./x11_client
# Should work on Wayland with XWayland
# Wayland
$ ./wayland_client
# Should work on Wayland session only
Common Pitfalls and Debugging
X11-Specific Issues
Problem: Window appears then disappears
Cause: Program exits before event loop Fix: Add event loop with XNextEvent()
Problem: No title showing
Cause: Not setting WM_NAME or window manager not reading it Fix: Also set _NET_WM_NAME (EWMH)
Problem: Close button doesnโt work
Cause: Not handling WM_DELETE_WINDOW Fix: XSetWMProtocols() and check ClientMessage
Debugging Tools
# Show window properties
$ xprop -id <window_id>
# Watch events
$ xev
# Show window tree
$ xwininfo -root -tree
# Protocol trace
$ xtrace ./x11_client
Interview Questions Comparison
After completing both P01 and P05, you should be able to answer:
- โWhy was Wayland created? What problems does it solve?โ
- X11โs server-side rendering is inefficient for modern GPUs
- X11โs security model allows any client to snoop
- X11 is designed for network, wasteful for local
- Wayland is simpler, more secure, better performance
- โWhatโs the fundamental difference in rendering?โ
- X11: Client sends draw commands, server executes and stores
- Wayland: Client renders to buffer, compositor displays
- โHow does resize handling differ?โ
- X11: ConfigureNotify โ Expose โ Client redraws to server
- Wayland: xdg_toplevel.configure โ Client creates new buffer โ attach โ commit
- โWhy is Wayland more secure?โ
- X11: Any client can XGetImage any window
- Wayland: Clients isolated, canโt see each otherโs buffers
- โWhatโs the trade-off?โ
- X11: Less client work, network transparent, mature
- Wayland: More client work, better performance/security, modern
Resources
X11 Documentation
| Resource | Purpose |
|---|---|
| Xlib Programming Manual | Complete reference |
| ICCCM specification | Window manager hints |
| EWMH specification | Modern WM hints |
| Xlib Reference Manual | Function reference |
Online Resources
- https://tronche.com/gui/x/xlib/ - Xlib reference
- freedesktop.org ICCCM/EWMH specs
- X.org documentation
Self-Assessment Checklist
Before considering this project complete, verify you can:
- Explain why X11 uses Expose events but Wayland doesnโt
- Describe what an X11 atom is and why they exist
- Write an X11 client from scratch
- Handle WM_DELETE_WINDOW correctly
- Compare lines of code and complexity with Wayland version
- Debug X11 issues using xprop, xev, xwininfo
- Explain the security difference between X11 and Wayland
- Describe the rendering model difference
- Run your X11 client under XWayland
- Articulate why Wayland was created
Completing this project alongside P01 gives you deep understanding of both display server architectures. You can now speak authoritatively about X11 vs Wayland, understanding the trade-offs and reasons behind Waylandโs design. This comparative knowledge is valuable in any Linux desktop or graphics role.