summaryrefslogtreecommitdiffhomepage
path: root/.rules/plan/phase-03-compositing.md
blob: a85d12bc217c44abd6b1162741e79834fdeaacc5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# Phase 3 — Compositing: Redirect & Capture Window Contents

---

## Step 3.1 — Query and enable the Composite extension

At startup (after becoming WM), call:

```c
XCompositeQueryExtension(dpy, &composite_event, &composite_error);
XCompositeQueryVersion(dpy, &major, &minor);
XCompositeRedirectSubwindows(dpy, root, CompositeRedirectManual);
```

Print the extension version to confirm.

**Verify:** Prints "Composite extension: 0.4" (or similar). Clients
launched into `:1` are now **invisible** (redirected to off-screen pixmaps
but nobody is painting them). This is expected — the WM is responsible for
compositing now.

---

## Step 3.2 — Name window pixmaps

When a window is mapped (in the `MapRequest` handler or after
`XMapWindow`), call:

```c
Pixmap pixmap = XCompositeNameWindowPixmap(dpy, win->xwin);
```

Store the `Pixmap` in the `WmWindow` struct. On `ConfigureNotify` (resize),
free the old pixmap and re-obtain it.

**Verify:** No visual change yet (windows are still invisible), but no X
errors are printed. Confirm pixmap handles are non-zero via log output.

---

## Step 3.3 — Read pixmap into a raylib Texture (CPU copy path)

For each mapped window with a valid pixmap:

1. Call `XGetImage(dpy, pixmap, 0, 0, width, height, AllPlanes, ZPixmap)`
   to read the pixel data.
2. Convert the BGRA data (X11 format) to RGBA.
3. Create a raylib `Image` from the buffer, then `LoadTextureFromImage()`.
4. Store the `Texture2D` in the `WmWindow` struct.

In the raylib draw loop, draw each managed window's texture at its
position using `DrawTexture()`.

**Verify:** `./bin/run.sh --clients` — `xeyes`, `xclock`, and `xterm`
appear as textured quads rendered by raylib on the dark blue background.
They are static snapshots (no live updates yet). The raylib window is the
compositor.

---

## Step 3.4 — Enable the Damage extension for live updates

At startup, query and enable Damage:

```c
XDamageQueryExtension(dpy, &damage_event, &damage_error);
```

For each managed window, create a damage object:

```c
win->damage = XDamageCreate(dpy, win->xwin, XDamageReportNonEmpty);
```

In the event loop, handle `DamageNotify`:

```c
if (ev.type == damage_event + XDamageNotify) {
    // mark the window dirty
    win->dirty = true;
}
```

When a window is dirty, before drawing, re-read the pixmap via
`XGetImage`, update the texture with `UpdateTexture()`, then call
`XDamageSubtract(dpy, win->damage, None, None)`.

**Verify:** `xeyes` follows your mouse cursor in real-time. `xclock`
ticks. Typing in `xterm` is visible immediately. This is a working (CPU-
copied) compositor.

---

## Step 3.5 — Handle window stacking order

Draw windows in the correct stacking order (bottom to top). Maintain the
list order to match X's stacking order. Use `XQueryTree()` at startup to
get the initial order, and update on `ConfigureNotify` with
`Above`/`Below` sibling fields, or re-query when the stack changes.

Also handle `XRaiseWindow` / `XLowerWindow` requests via
`ConfigureRequest`.

**Verify:** Overlapping windows draw in the correct order. Clicking
`xterm` (if focus-follows-click is wired) or manually calling
`DISPLAY=:1 xdotool windowraise <id>` changes the draw order.