Project 5: X11 Comparison Project - Bare-Metal X11 Client
Build the same colored window as Project 1, but with raw Xlib, to feel the architectural differences between X11 and Wayland.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 2: Intermediate |
| Time Estimate | 1 week |
| Main Programming Language | C (Alternatives: C++, Rust, Zig) |
| Alternative Programming Languages | C++, Rust, Zig |
| Coolness Level | Level 3: Genuinely Clever |
| Business Potential | Level 1: The “Resume Gold” |
| Prerequisites | Project 1, basic Xlib usage, event loops |
| Key Topics | X11 protocol model, Xlib events, atoms/properties, ICCCM/EWMH |
1. Learning Objectives
By completing this project, you will:
- Implement a minimal X11 client that opens a window and draws pixels.
- Handle Expose events and understand why X11 is server-retained.
- Use atoms and properties to set window metadata and close handling.
- Compare X11 request/reply flow with Wayland’s event-driven model.
- Explain why Wayland moved policy into the compositor.
2. All Theory Needed (Per-Concept Breakdown)
2.1 X11 Client-Server Model and Request/Reply Semantics
Fundamentals
X11 is a network-transparent windowing system based on a client-server model. The X server owns the display and input devices, and clients send requests to create windows, draw graphics, and receive events. Unlike Wayland, X11 exposes a rich drawing API and retains window contents server-side. The client sends requests, and the server may reply or generate events asynchronously. Understanding this model is essential because it explains why X11 requires Expose handling and why it can be more chatty than Wayland. Extensions and access controls add more complexity and are part of the real-world X11 story. It also defines how drawing and input are multiplexed.
Deep Dive into the concept
The X server is a long-lived process that mediates access to the display and input devices. Clients connect over a UNIX socket (or TCP), then issue requests like XCreateWindow, XMapWindow, or XDrawRectangle. Many requests are asynchronous: the client writes to a socket, and the server processes them later. Some requests require a reply, which forces synchronization. This request/reply model can introduce latency when clients block waiting for the server. Xlib tries to hide this by buffering requests and flushing them only when needed, but as a developer you must understand when synchronization happens.
X11 is also server-retained. The server maintains window contents (unless the client uses a special extension). This means that when a window is uncovered, the server can ask the client to redraw via an Expose event. This model is different from Wayland, where clients always own their buffers. The retained model is convenient but creates complexity: the client must handle Expose events correctly, or parts of the window will appear blank. It also allows the server to introspect or capture window contents, which has security implications.
Because X11 is network-transparent, the protocol includes explicit resource IDs, byte-order negotiation, and a large number of requests and events. The protocol is stable but complex. It has accumulated decades of extensions, which is both a strength and a source of confusion. For this project, you will use only a tiny subset: create a window, select events, draw a rectangle, and set window properties. But even that subset demonstrates the complexity of atoms, properties, and the window manager conventions that sit on top of the core protocol.
The request/reply model also affects how you write your event loop. You can send requests without waiting, but you must eventually flush. If you call a function that waits for a reply (like XGetWindowAttributes), you will block until the server responds. This can make input feel laggy if used in the hot path. For a minimal client, you can avoid replies entirely and rely on events instead.
Finally, X11’s model assumes the server is trusted. Any client can ask the server for input state or screenshot other windows unless restricted by access controls. This is one of the primary reasons Wayland was designed: to move policy and security into the compositor and restrict what clients can see. By implementing a bare-metal X11 client, you will see the flexibility of the X11 model and understand why it is also problematic.
Another key aspect is synchronization. Xlib buffers requests and only sends them on XFlush or when it needs a reply. XSync can be used to force a round trip, which is helpful for debugging but expensive. Every round trip adds latency, especially over a network. This is why many X11 apps avoid querying the server in the hot path. Sequence numbers and error handlers also exist at the protocol layer; if you send a malformed request, the error may arrive later, disconnected from the call site. That delayed error model can be surprising if you are used to synchronous APIs.
How this fit on projects
This concept supports Section 3.1 (what you build), Section 3.2 (functional requirements), and Section 5.3 (core question). It is the baseline for comparison with Wayland.
Definitions & key terms
- X server -> display server that owns the screen and input
- request -> client-to-server command
- reply -> server response to a request
- Expose event -> request to redraw a window region
- network transparency -> ability to run clients remotely
Mental Model Diagram (ASCII)
Client X Server Display
| requests ---> | draw/window state ---> screen
| events <--- |
How It Works (Step-by-Step)
- Client connects to X server with XOpenDisplay.
- Client sends requests to create and map a window.
- Server sends events (Expose, KeyPress, ConfigureNotify).
- Client redraws on Expose.
Invariants:
- Server owns window contents; client must redraw when asked.
- Requests are buffered until flushed.
Failure modes:
- No Expose handling -> blank or corrupted window.
- Blocking on replies -> laggy UI.
Minimal Concrete Example
Display *d = XOpenDisplay(NULL);
Window w = XCreateSimpleWindow(d, DefaultRootWindow(d), 10,10,400,300,1,
BlackPixel(d,0), WhitePixel(d,0));
XMapWindow(d, w);
XFlush(d);
Common Misconceptions
- “X11 draws for me.” -> X11 provides drawing primitives, but you must issue them.
- “Requests are synchronous.” -> Many are buffered and asynchronous.
Check-Your-Understanding Questions
- Why does X11 need Expose events?
- What happens if you never call XFlush?
- Why is X11 called network-transparent?
Check-Your-Understanding Answers
- The server retains window contents and asks clients to redraw when exposed.
- Requests stay in the buffer and never reach the server.
- Because clients can run on remote machines and connect over the network.
Real-World Applications
- Legacy desktop environments and remote X11 sessions use this model.
Where You’ll Apply It
- In this project: Section 3.2, Section 5.3, Section 7.1.
- Also used in: P01 Bare-Metal Wayland Client for comparison.
References
- X11 protocol specification
- Xlib Programming Manual (O’Reilly)
Key Insights
X11’s retained, request/reply model enables flexibility but creates complexity and security trade-offs.
Summary
X11 is a client-server system with buffered requests and server-retained window contents.
Homework/Exercises to Practice the Concept
- Write a program that connects to X and maps a window.
- Add XFlush and observe the difference.
- Run the client over SSH with X forwarding.
Solutions to the Homework/Exercises
- Use XOpenDisplay, XCreateSimpleWindow, XMapWindow.
- Without XFlush, the window may not appear.
- Use ssh -X and set DISPLAY accordingly.
2.2 Xlib Event Loop and Expose-Driven Redraw
Fundamentals
Xlib delivers events to the client through an event queue. You call XNextEvent to block until an event arrives, then handle it. The most important event for drawing is Expose, which tells you that a region of the window needs repainting. In X11, you must redraw on Expose because the server does not automatically redraw your content. This is different from Wayland, where you render on frame callbacks and buffer commits. Selecting the right masks up front is the difference between a responsive client and a silent one. Selecting masks early avoids missing events. This is why Expose handling is central.
Deep Dive into the concept
The Xlib event loop is simple but has subtle rules. When a window is mapped, the server may immediately send Expose events for the newly visible regions. If you do not handle them, your window stays blank. A robust client handles Expose by redrawing the entire window or at least the exposed region. The Expose event includes x, y, width, and height, so you can redraw only the damaged region for efficiency. However, for simple apps, redrawing everything is acceptable.
The event loop also handles input events like KeyPress, ButtonPress, and ConfigureNotify (resize). If the window is resized, you should update your internal size and redraw. Unlike Wayland, where the compositor sends configure events before mapping, X11 sends ConfigureNotify after the resize. This means your code reacts after the fact. This difference highlights the Wayland design: the compositor is in control, while X11 clients are more reactive.
Xlib also has a concept of exposures that can be combined. If multiple Expose events are queued, you can wait until the last one (with event.xexpose.count == 0) and then redraw once. This avoids redundant drawing. In a minimal client, you can ignore this and redraw on every Expose, but the optimization demonstrates how clients can reduce workload when the server floods them with events.
Event handling also interacts with flushing. Xlib buffers output requests; you typically call XFlush after drawing to ensure the server processes them. If you draw but do not flush, nothing appears until the buffer fills or a sync happens. This is another reason why X11 can feel more manual compared to Wayland. A good mental model is: process events, update state, draw, flush.
If you need a non-blocking loop, you can use XPending to check whether events are available, or XCheckWindowEvent to poll for specific events. This lets you integrate Xlib into a custom event loop alongside other file descriptors. However, you must be careful to avoid busy loops; a small sleep or a select on the X connection fd is better. Xlib exposes the connection file descriptor with ConnectionNumber(dpy), which you can use with select/poll. This is similar to wl_display_get_fd in Wayland, but Xlib’s buffering makes it more subtle.
Drawing in Xlib typically uses a GC (graphics context). The GC stores drawing parameters like foreground color, line width, and font. You create a GC once and reuse it for drawing operations. If you change GC state, it affects subsequent draw calls. This is efficient but can be confusing if you are not used to retained drawing state. For a simple client, you can set the foreground color once and call XFillRectangle. For more complex clients, you must manage GC state carefully to avoid drawing with unexpected colors or fonts.
Expose storms can happen when windows are rapidly resized or uncovered. The X server may queue many Expose events, and if you draw on each one you waste CPU. A common pattern is to redraw only when ev.xexpose.count == 0, which indicates the last Expose in a batch. This keeps your client responsive. If you later switch to pixel-level rendering with XImage, you will still follow the same event loop, but your draw step will upload a pixel buffer instead of issuing drawing primitives. That is a good stepping stone to understanding how Wayland clients render directly.
How this fit on projects
This concept is used in Section 3.2 (functional requirements), Section 5.10 (implementation phases), and Section 7 (common pitfalls).
Definitions & key terms
- Expose -> event indicating region needs redraw
- XNextEvent -> blocking call to fetch next event
- ConfigureNotify -> event indicating window resize or move
- event queue -> buffered events from the server
Mental Model Diagram (ASCII)
X server --> event queue --> XNextEvent() loop --> redraw
How It Works (Step-by-Step)
- Select input events with XSelectInput.
- Enter loop calling XNextEvent.
- On Expose, redraw window.
- On ConfigureNotify, update size and redraw.
Invariants:
- Redraw when Expose arrives.
- Flush after drawing.
Failure modes:
- Ignoring Expose -> blank window.
- Missing ConfigureNotify -> wrong size rendering.
Minimal Concrete Example
XSelectInput(d, w, ExposureMask | KeyPressMask | StructureNotifyMask);
for (;;) {
XEvent ev; XNextEvent(d, &ev);
if (ev.type == Expose) draw();
}
Common Misconceptions
- “Expose is optional.” -> Without it your window may not paint.
- “Xlib draws immediately.” -> It buffers; you must flush.
Check-Your-Understanding Questions
- Why does Expose exist in X11?
- What is ev.xexpose.count used for?
- Why does ConfigureNotify come after resize?
Check-Your-Understanding Answers
- Because the server needs the client to redraw exposed regions.
- It indicates how many more Expose events are pending.
- Because X11 is reactive to server changes.
Real-World Applications
- All traditional Xlib apps use Expose handling.
Where You’ll Apply It
- In this project: Section 3.2, Section 5.10 Phase 2, Section 7.1.
- Also used in: P01 Bare-Metal Wayland Client as contrast.
References
- Xlib Programming Manual, Chapter on Events
Key Insights
Expose-driven redraw is the heart of the X11 client model.
Summary
Process events, redraw on Expose, and flush requests to the server.
Homework/Exercises to Practice the Concept
- Handle Expose and draw a rectangle.
- Redraw only when ev.xexpose.count == 0.
- Add resize handling using ConfigureNotify.
Solutions to the Homework/Exercises
- Draw in the Expose handler.
- Check ev.xexpose.count and redraw once.
- Update width/height and redraw.
2.3 Atoms, Properties, ICCCM, and EWMH
Fundamentals
X11 uses atoms to identify properties, which are key/value metadata attached to windows. Atoms are integer IDs that represent strings like WM_NAME or _NET_WM_PID. Window managers use these properties to decide how to decorate and manage windows. ICCCM and EWMH are conventions that specify which properties to set and how to interpret them. A well-behaved client sets WM_NAME and handles WM_DELETE_WINDOW so the window manager can close it cleanly. Tools like xprop make these properties visible, which is useful for debugging. Properties are often inspected during troubleshooting. These properties affect taskbars and grouping.
Deep Dive into the concept
Atoms are the indirection layer that makes X11 flexible. Instead of sending long strings in every request, the client interns a string into an Atom ID using XInternAtom. That ID is then used to set or query properties. Properties are arbitrary blobs of data attached to a window, and the meaning of each property is defined by conventions like ICCCM (Inter-Client Communication Conventions Manual) and EWMH (Extended Window Manager Hints). For example, WM_NAME sets the window title, WM_CLASS identifies the application, and _NET_WM_PID reports the process ID. Setting these is not optional if you want your window to behave correctly under modern window managers.
WM_DELETE_WINDOW is a classic example. The window manager sends a ClientMessage event when the user clicks the close button, but only if the client has registered for the WM_DELETE_WINDOW protocol. That registration is done by setting the WM_PROTOCOLS property to include the WM_DELETE_WINDOW atom. If you skip this, the window manager may kill your client without giving you a chance to clean up. This is why every Xlib example includes this step.
EWMH adds more metadata: window types, states, and hints for taskbars. You do not need the full EWMH set for a minimal client, but it is important to know that many behaviors are not part of core X11; they are conventions. This highlights a core contrast with Wayland, where many policies are part of the compositor protocol itself. In X11, the window manager interprets properties, and different window managers can behave differently. That flexibility is powerful but also inconsistent.
Another subtlety is property types and formats. Properties are typed (e.g., ATOM, STRING, CARDINAL) and have a format (8, 16, or 32 bits). If you set the wrong type or format, the window manager may ignore your property. This makes it important to read the ICCCM/EWMH specs or use helper functions that set properties correctly. In a minimal project, you can set WM_NAME as a STRING and WM_DELETE_WINDOW as an ATOM, but understanding the format matters if you extend your client.
Modern window managers often prefer UTF8_STRING and _NET_WM_NAME for titles instead of the legacy STRING type. If you set only WM_NAME, your title may appear correctly, but some environments will use _NET_WM_NAME for better Unicode handling. Similarly, WM_CLASS is used for grouping windows and applying rules. Even if you keep your client minimal, it is helpful to know that these properties exist and that correct types matter. This illustrates a general X11 pattern: correct behavior often depends on conventions, not just core protocol rules.
Some properties are also used by taskbars and pagers. For example, _NET_WM_WINDOW_TYPE can hint whether a window is a normal app, dialog, or dock. Setting it incorrectly can change how your window is presented. For a minimal client, you can skip it, but knowing it exists helps you interpret why different window managers behave differently. It reinforces the idea that X11’s behavior is negotiated through conventions layered on top of the core protocol.
How this fit on projects
This concept drives Section 3.2 (functional requirements) and Section 7 (pitfalls). It also explains why your X11 client needs more boilerplate than the Wayland client.
Definitions & key terms
- atom -> integer ID representing a string
- property -> key/value metadata on a window
- ICCCM -> conventions for inter-client communication
- EWMH -> extended window manager hints
Mental Model Diagram (ASCII)
Client -> XInternAtom("WM_NAME") -> atom id
Client -> XChangeProperty(window, atom id, value)
WM reads property -> sets title
How It Works (Step-by-Step)
- Intern atoms for WM_NAME and WM_DELETE_WINDOW.
- Set WM_NAME property on the window.
- Set WM_PROTOCOLS to include WM_DELETE_WINDOW.
- Handle ClientMessage events for close.
Invariants:
- Properties must use correct type and format.
- WM_DELETE handling must be registered before mapping.
Failure modes:
- Missing WM_DELETE -> forced kill on close.
- Wrong property type -> window manager ignores metadata.
Minimal Concrete Example
Atom wm_delete = XInternAtom(d, "WM_DELETE_WINDOW", False);
XSetWMProtocols(d, w, &wm_delete, 1);
XStoreName(d, w, "X11 Client");
Common Misconceptions
- “Properties are optional.” -> Window managers depend on them.
- “EWMH is part of Xlib.” -> It is a convention, not a library.
Check-Your-Understanding Questions
- Why do we use atoms instead of raw strings?
- What happens if WM_DELETE_WINDOW is not set?
- Why can different window managers behave differently?
Check-Your-Understanding Answers
- Atoms avoid sending long strings repeatedly and standardize IDs.
- The window manager may kill the client without notice.
- Because ICCCM/EWMH are conventions, not enforced by the protocol.
Real-World Applications
- Every X11 GUI app sets WM_NAME and WM_CLASS.
Where You’ll Apply It
- In this project: Section 3.2, Section 5.10 Phase 2, Section 7.1.
- Also used in: P05 X11 Client itself.
References
- ICCCM specification
- EWMH specification
Key Insights
Atoms and properties are the metadata layer that makes X11 windows manageable.
Summary
Use atoms and properties to set window metadata and handle close requests properly.
Homework/Exercises to Practice the Concept
- Set WM_NAME and WM_CLASS and verify with xprop.
- Register WM_DELETE_WINDOW and log ClientMessage.
- Add _NET_WM_PID property.
Solutions to the Homework/Exercises
- Use XStoreName and XSetClassHint.
- Add WM_PROTOCOLS and handle ClientMessage.
- Use XChangeProperty with CARDINAL type.
2.4 Input Focus, Key Handling, and Cleanup
Fundamentals
X11 input events are delivered based on event masks you select. You must choose which events you want to receive and then interpret keycodes using Xlib helpers. Focus is handled by the window manager, and your client receives KeyPress events only when focused. You should handle the Escape key for a clean exit and also respond to WM_DELETE_WINDOW. This combination ensures your app closes gracefully. Layout changes and focus transitions can happen at any time, so treat input handling as dynamic. You can also handle FocusIn and FocusOut for better UX. Handle KeyRelease if you track key state.
Deep Dive into the concept
X11 input is a layered system. The server sends events to a window based on the selected event masks. If you forget to select KeyPressMask, you will never receive keyboard input. Once you receive a KeyPress, you need to translate the keycode to a keysym using XLookupKeysym or XkbKeycodeToKeysym. This translation depends on the active keyboard layout, which is managed by the server and the window manager.
Focus is important. X11 allows different focus policies (click-to-focus, focus-follows-mouse) controlled by the window manager. Your client does not choose focus; it reacts to it. This is another difference from Wayland, where the compositor explicitly routes input. In X11, focus policy is external to the client, which can lead to inconsistent behaviors across desktop environments.
Cleanup in X11 is both simple and tricky. When you receive WM_DELETE_WINDOW, you should destroy your window and close the display. If you just call exit() without cleaning up, the server will clean up, but you may leak resources or leave pending requests. For a minimal client, calling XDestroyWindow and XCloseDisplay is enough. You should also flush before closing to ensure the server processes your destroy request.
Another subtlety is that X11 lets any client query global input state unless access controls are enabled. This is one reason Wayland tightened input security. By implementing an X11 client, you can appreciate both the flexibility and the security risk: you can ask the server for pointer position even when your window is not focused. This power is useful but dangerous.
Focus and key repeat deserve special attention. X11 can send FocusIn and FocusOut events when focus changes. If you want to pause animations or stop handling keyboard input when unfocused, you can listen for those events. Key repeat in X11 is generated by the server, not the client, which means you may see repeated KeyPress events without matching KeyRelease events depending on server configuration. If you need precise key state tracking, you must handle these cases carefully or use the XKB extension. For a minimal client, you can ignore the complexity, but understanding it helps you avoid confusing behavior when you later add shortcuts.
Cleanup is also more than just XDestroyWindow. If you allocate resources like Pixmaps, GCs, or fonts, you must free them explicitly. The server will clean up after a client disconnects, but doing explicit cleanup avoids resource accumulation during long sessions. This is another difference from Wayland: because X11 exposes many server-side resources, there are more opportunities to leak them. A disciplined cleanup path keeps your client well-behaved and makes debugging easier if you later expand the project.
X11 also allows explicit grabs. A client can call XGrabPointer or XGrabKeyboard to receive all input, even when not focused. This is useful for drag operations and modal dialogs, but it can also create a poor user experience if misused. Window managers may override or deny grabs depending on policy. This again shows the difference between X11’s permissive model and Wayland’s stricter input routing. Even if you do not use grabs in this project, understanding them helps you interpret odd focus behavior.
How this fit on projects
This concept is used in Section 3.2, Section 5.10, and Section 7.1. It also provides a clear contrast with Wayland input routing.
Definitions & key terms
- KeyPressMask -> event mask for keyboard input
- keysym -> symbolic representation of a key
- focus policy -> window manager rule for focus
- WM_DELETE_WINDOW -> close request protocol
Mental Model Diagram (ASCII)
Keyboard -> X server -> event queue -> client
How It Works (Step-by-Step)
- Select KeyPressMask and ExposureMask.
- On KeyPress, translate keycode to keysym.
- On Escape, exit cleanly.
- On WM_DELETE_WINDOW, destroy window and close display.
Invariants:
- Events only arrive if mask is selected.
- Focus is controlled externally.
Failure modes:
- Missing KeyPressMask -> no input.
- Ignoring WM_DELETE -> forced kill.
Minimal Concrete Example
XSelectInput(d, w, ExposureMask | KeyPressMask);
if (ev.type == KeyPress) {
KeySym ks = XLookupKeysym(&ev.xkey, 0);
if (ks == XK_Escape) quit = 1;
}
Common Misconceptions
- “Clients control focus.” -> Window manager controls focus.
- “Any keypress is delivered.” -> Only if you select the mask.
Check-Your-Understanding Questions
- Why must you select KeyPressMask?
- What does XLookupKeysym do?
- Why is WM_DELETE_WINDOW preferred to killing the window?
Check-Your-Understanding Answers
- The server only delivers events you select.
- It maps keycodes to symbolic keys respecting layout.
- It allows graceful cleanup.
Real-World Applications
- X11 apps use the same masks and key translation.
Where You’ll Apply It
- In this project: Section 3.2, Section 5.10 Phase 2, Section 7.1.
- Also used in: P01 Bare-Metal Wayland Client for comparison.
References
- Xlib Programming Manual, input chapters
Key Insights
X11 input is powerful but depends on global policies outside your control.
Summary
Select event masks, translate keys, and exit cleanly with WM_DELETE handling.
Homework/Exercises to Practice the Concept
- Implement ESC to quit.
- Print all keysyms on keypress.
- Handle WM_DELETE_WINDOW gracefully.
Solutions to the Homework/Exercises
- Check for XK_Escape.
- Use XLookupKeysym and print the name.
- Set WM_PROTOCOLS and handle ClientMessage.
3. Project Specification
3.1 What You Will Build
A minimal X11 client that:
- Connects to the X server
- Creates a window with a solid color background
- Handles Expose events to redraw
- Sets WM_NAME and WM_DELETE_WINDOW
- Exits on Escape or close
Excluded:
- Advanced X11 extensions
- Complex rendering
3.2 Functional Requirements
- Connection: Open display and create a window.
- Event loop: Handle Expose and KeyPress events.
- Rendering: Draw a solid color rectangle on Expose.
- Metadata: Set WM_NAME and WM_DELETE_WINDOW.
- Cleanup: Destroy window and close display on exit.
3.3 Non-Functional Requirements
- Performance: Idle CPU near zero.
- Reliability: No crashes on resize or close.
- Usability: Close via Escape or window manager.
3.4 Example Usage / Output
$ gcc -Wall -O2 -o x11_client x11_client.c -lX11
$ ./x11_client
X11 client starting...
3.5 Data Formats / Schemas / Protocols
- X11 core protocol
- ICCCM/EWMH properties
3.6 Edge Cases
- DISPLAY not set -> exit with error
- Expose flood -> redraw once when count==0
3.7 Real World Outcome
3.7.1 How to Run (Copy/Paste)
cc -O2 -Wall -o x11_client x11_client.c -lX11
./x11_client
3.7.2 Golden Path Demo (Deterministic)
- Fixed color: #3366cc
- Fixed window title: “X11 Client”
3.7.3 If CLI: Exact Terminal Transcript
$ ./x11_client
X11 client starting...
Display: :0
Mapped window id=0x3c00007
Expose event: repainting
Press ESC or close the window to exit
Exit codes:
- 0 on clean exit
- 1 if DISPLAY not available
3.7.4 If GUI / Desktop
ASCII wireframe:
+----------------------------------------+
| X11 Client [X]|
| |
| (solid color fill) |
| |
+----------------------------------------+
3.7.5 Failure Demo (Deterministic)
$ DISPLAY= ./x11_client
[Error] DISPLAY not set
[Exit] code=1
4. Solution Architecture
4.1 High-Level Design
X11 client -> X server -> window manager -> display
4.2 Key Components
| Component | Responsibility | Key Decisions | |———–|—————-|—————| | Xlib setup | connect, create window | use XCreateSimpleWindow | | Event loop | handle events | Expose + KeyPress + ClientMessage | | Renderer | draw solid color | XFillRectangle |
4.3 Data Structures (No Full Code)
struct app {
Display *d;
Window w;
GC gc;
Atom wm_delete;
int width, height;
};
4.4 Algorithm Overview
Key Algorithm: Expose Redraw
- Wait for Expose.
- Draw rectangle covering window.
- Flush requests.
Complexity Analysis:
- Time: O(width * height) for fill
- Space: O(1)
5. Implementation Guide
5.1 Development Environment Setup
sudo apt install libx11-dev
5.2 Project Structure
x11-client/
|-- src/
| `-- x11_client.c
`-- Makefile
5.3 The Core Question You’re Answering
“How does an X11 client differ from a Wayland client in who draws and who controls policy?”
5.4 Concepts You Must Understand First
- X11 request/reply model
- Expose-driven redraw
- Atoms and properties
- Focus and key handling
5.5 Questions to Guide Your Design
- Which events must you select to redraw and exit?
- How will you set WM_NAME and WM_DELETE_WINDOW?
- How will you handle Expose floods efficiently?
5.6 Thinking Exercise
Write the sequence of messages that occur when you map the window and then cover/uncover it.
5.7 The Interview Questions They’ll Ask
- Why does X11 require Expose events?
- What is an atom and why does X11 use them?
- How does X11 differ from Wayland in terms of security?
5.8 Hints in Layers
Hint 1: Minimal window Start with XOpenDisplay, XCreateSimpleWindow, XMapWindow.
Hint 2: Expose handling Only draw when ev.type == Expose.
5.9 Books That Will Help
| Topic | Book | Chapter | |——-|——|———| | Xlib basics | Xlib Programming Manual | Ch. 1-3 | | UNIX IPC | Advanced Programming in the UNIX Environment | Ch. 14-18 |
5.10 Implementation Phases
Phase 1: Foundation (2-3 days)
Goals:
- Open display
- Create and map window
Tasks:
- Connect to X server.
- Create window and map.
Checkpoint: Window appears.
Phase 2: Core Functionality (3-4 days)
Goals:
- Handle Expose and KeyPress
- Draw solid color
Tasks:
- Implement Expose redraw.
- Handle Escape key to exit.
Checkpoint: Window repaints and exits cleanly.
Phase 3: Polish and Edge Cases (1-2 days)
Goals:
- Set WM properties
- Handle WM_DELETE_WINDOW
Tasks:
- Set WM_NAME and WM_PROTOCOLS.
- Handle ClientMessage.
Checkpoint: Close button exits cleanly.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale | |———-|———|—————-|———–| | Drawing API | Xlib primitives, XImage | XFillRectangle | simplest | | Redraw strategy | Expose-only, continuous | Expose-only | efficient | | Close handling | kill, WM_DELETE_WINDOW | WM_DELETE_WINDOW | graceful |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples | |———-|———|———-| | Unit Tests | Validate helpers | keysym mapping | | Integration Tests | Event flow | Expose -> redraw | | Edge Case Tests | Missing DISPLAY | error handling |
6.2 Critical Test Cases
- Expose event: redraw happens once.
- Escape key: exits cleanly.
- WM_DELETE: close button exits.
6.3 Test Data
color=#3366cc
title="X11 Client"
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution | |———|———|———-| | No Expose handling | blank window | redraw on Expose | | Missing WM_DELETE | forced kill | set WM_PROTOCOLS | | No XFlush | nothing drawn | call XFlush after drawing |
7.2 Debugging Strategies
- Use xev to inspect events.
- Use xprop to inspect window properties.
7.3 Performance Traps
- Redrawing continuously wastes CPU.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a second color mode toggled by keypress.
8.2 Intermediate Extensions
- Draw text with XDrawString.
8.3 Advanced Extensions
- Use XImage to draw a pixel buffer.
9. Real-World Connections
9.1 Industry Applications
- Legacy X11 applications and remote desktops.
9.2 Related Open Source Projects
- xclock: simple X11 app with Expose handling
9.3 Interview Relevance
- Compare request/reply with Wayland async model.
10. Resources
10.1 Essential Reading
- Xlib Programming Manual
- X11 Protocol Specification
10.2 Video Resources
- X11 architecture talks
10.3 Tools & Documentation
- xev: inspect X11 events
- xprop: inspect window properties
10.4 Related Projects in This Series
11. Self-Assessment Checklist
11.1 Understanding
- I can explain why X11 needs Expose events.
- I can explain what atoms are and why they matter.
- I can describe the request/reply model.
11.2 Implementation
- Window appears and repaints correctly.
- WM_DELETE_WINDOW works.
- Escape exits cleanly.
11.3 Growth
- I can compare X11 vs Wayland in an interview.
- I can explain security differences between them.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Window appears with solid color.
- Expose and KeyPress handled.
- WM_DELETE_WINDOW works.
Full Completion:
- All minimum criteria plus:
- Correct WM_NAME and WM_CLASS properties.
- Expose flood optimization (count==0).
Excellence (Going Above & Beyond):
- Add XImage rendering path.
- Document comparison notes in a README.