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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
|
# Implementation status
This mirrors the style of the legacy C++ project's own status doc (now at
`legacy-cpp/docs/IMPLEMENTATION_STATUS.md`), but for the Rust rewrite.
"Verified" means: built with `cargo test --workspace` (0 warnings under
`cargo clippy --workspace`) and, where applicable, actually run and observed
doing the thing described - not just "the code compiles and looks right."
## ✅ Complete and verified
### Core window/workspace/layout engine (`crates/core`)
- `WindowManager`: window/workspace/monitor state, focus cycling, directional
focus (`Direction::{Left,Right,Up,Down}`), drag/resize state machine,
hit-testing shared by every backend.
- `MasterStackLayout`: real dwm-style master/stack tiling with configurable
ratio and gaps (the legacy C++ tiling layout only ever split windows into
equal-width columns, ignoring its own documented `master_ratio` config key).
- `SmartPlacement`: grid placement with real per-cell occupancy tracking,
diagonal cascade fallback, and Windows-Snap-style edge magnetism
(half/quarter/maximize zones). The legacy C++ version's grid placement
used a `static` round-robin counter that hardcoded a 2-column layout
regardless of window count; its cascade never actually cascaded; its
snap-to-edge always returned a fixed centered rectangle.
- 35 unit tests, deterministic (window arrangement no longer depends on
`HashMap` iteration order - an early version of `arrange_workspace` did,
and it was caught by a flaky test during this rewrite; see the fix in
`crates/core/src/manager.rs`).
### Lua config engine (`crates/config`)
- Full `srd` API matching `docs/DEFAULTS.md`'s documented (not the legacy
C++'s actually-implemented) surface: `srd.set/get/reset/reset_all/reset_category`,
`srd.window.{focused,close,minimize,maximize,focus,set_decorations,
set_border_color,set_border_width,set_floating,toggle_floating,is_floating}`,
`srd.layout.{set,configure}`, `srd.workspace.{next,prev,switch,move_window}`,
`srd.theme.{set_colors,set_decorations}`, `srd.bind`, `srd.load`,
`srd.spawn`, `srd.notify`, `srd.quit`, `srd.rule`, `srd.validate_config`,
`srd.debug.{config_status,validate_config,show_settings,profile_start,profile_stop}`.
- `srd.bind()` stores the actual Lua closure via `mlua`'s registry and
invokes it on dispatch (the legacy engine stored only the key-combo
string - keybindings could never fire).
- `srd.rule(matcher, actions)` matches windows by title (substring) or
class/app_id (exact) and applies floating/maximized/workspace/geometry/
decoration/border actions once, when a matching window is first created
(`crates/core/src/rules.rs`, applied from `WindowManager::add_window`).
`config/srd/rules.lua` documents the API instead of being a no-op
placeholder.
- `srd.validate_config()` (and `srd.debug.validate_config()`) actually
check the numeric ranges, layout-name references, and hex-color formats
documented in `docs/DEFAULTS.md`'s "Validation Rules" section, returning
`(ok, errors)` - not a trivial always-true. `srd.debug.config_status()`/
`show_settings()`/`profile_start()`/`profile_stop()` are real too.
- `local srd = require("srd")` works (registered via `package.preload`, not
just as a global) - every shipped example config opens with this line,
and it would have failed against a naive "global-only" registration; this
was caught and fixed during the smoke test.
- 15 unit tests, including one that reproduces the exact legacy bug
(`window:close()` on a table with no methods) and shows it now works.
### X11 backend (`crates/x11`)
- Real reparenting WM: frame windows sized to actual client geometry (not
the legacy's hardcoded 800px titlebar), drawn title bar with
close/maximize/minimize buttons, drag-to-move, edge/corner resize --
all driven by the same `ResizeEdge::hit_test` the Wayland backend uses.
- Correct other-WM detection via a *checked* `SUBSTRUCTURE_REDIRECT`
request (the legacy version's error handler discarded errors and always
reported success).
- RandR monitor enumeration using CRTC pixel mode, not output physical
millimeters (the legacy version conflated the two).
- `WM_DELETE_WINDOW`-aware close, click-to-focus via a passive button grab
+ replay (the standard dwm/openbox pattern), global keybinding grabs
translated from Lua combo strings via a hand-maintained keysym table
(`crates/x11/src/keysyms.rs`, letters/digits/navigation/F-keys/media keys;
not a full xkbcommon keymap).
- **Verified live**: run under Xephyr with the shipped example config, an
`xterm` client was correctly reparented (frame at the exact
`SmartPlacement`-computed position, client offset by exactly
`TITLEBAR_HEIGHT`), and the drawn title bar (background, title text,
minimize/maximize/close glyphs) was confirmed via screenshot.
- **Re-verified in an isolated QEMU VM** (see "QEMU VM verification" below):
two `xterm` clients reparented and placed by `SmartPlacement`, each with
a drawn titlebar showing real title text and close/maximize/minimize
glyphs, screenshotted via QEMU's `screendump`.
### Windows and macOS backends (`crates/windows`, `crates/macos`)
- Structured as honest stubs: real-looking `windows-rs`/Core Graphics calls
behind `cfg(windows)` / `cfg(target_os = "macos")`, but **never built or
run** - this sandbox only has the `x86_64-unknown-linux-gnu` target
installed. On any other target the same methods return
`PlatformError::Unsupported` rather than pretending to work.
- Design intent (informed by komorebi/glazewm for Windows, yabai/AeroSpace
for macOS - see `docs/PRIOR_ART.md`) is documented in each crate's module
doc comment: keep DWM's native frame on Windows rather than fight it;
use the public Accessibility API plus an overlay window for decorations
on macOS, not private APIs.
## 🔄 Wayland backend (`crates/wayland`) - real, more limited scope than X11
This is the one piece with essentially no working prior art to port (see
`docs/PRIOR_ART.md`): the legacy C++ never wired a single event listener.
What's here is a genuine from-scratch `smithay`-based compositor, not a stub:
- ✅ Runs via smithay's winit backend (nested window), initializes EGL/GLES,
advertises a real Wayland socket, and was verified to start, initialize
rendering, and run its event loop without crashing (log-verified; a
full visual confirmation the way X11 got one was skipped deliberately --
see below).
- ✅ xdg-shell toplevels are tracked through the *same*
`srdwm_core::WindowManager` the X11 backend uses - new windows get a
real `WindowId`, go through `SmartPlacement`/`MasterStackLayout` exactly
like X11 windows do.
- ✅ xdg-decoration is negotiated to server-side mode.
- ✅ Pointer click/drag/resize on the decoration band uses the identical
`hit_test` code path as X11.
- ✅ Decorations render actual title text (`crates/wayland/src/decoration.rs`):
glyphs rasterized via `fontdue` against whatever monospace font is found
under `/usr/share/fonts` etc. (falls back to solid-color-only, same as
before, if none is found), uploaded per-frame through smithay's
`MemoryRenderBuffer`. Pure `(width, height, text) -> Vec<u8>` function,
unit-tested without any GL/display context.
- ✅ Global keybindings are matched precisely: `WaylandPlatform::connect`
takes the config's actual bound-key combo strings (same format/shared
`srdwm_core::keysyms` table the X11 backend's `XGrabKey` calls use) and
only a matching keypress is withheld from the focused client - no more
"any Super-held key is ours" heuristic.
- ✅ DRM/udev backend (`crates/wayland/src/udev.rs`): runs as the real
compositor on a bare TTY, no host session to nest under. Single primary
GPU, first connected connector, its first-listed mode, real `libseat`
session/seat handling (VT-switch pause/resume, no raw root-only
`/dev/dri` open), real `libinput` input sharing the exact same
keybinding/hit-test code the winit backend uses. Rendering is
**software** (smithay's `PixmanRenderer` into plain KMS dumb buffers via
the legacy, non-atomic `set_crtc`/`page_flip` API) rather than
GBM/EGL/`DrmCompositor`-based hardware acceleration: that path needs a
GPU with working KMS+3D driver support that a low-spec machine's VM isn't
guaranteed to have, while dumb buffers work on essentially any DRM
driver. `WaylandPlatform::connect` (winit) picks this backend
automatically when no `WAYLAND_DISPLAY`/`DISPLAY` is set, falling back to
nested winit if udev init fails for any reason.
**Verified live in an isolated QEMU VM** (see below): started on a bare
virtual TTY with no `DISPLAY`/`WAYLAND_DISPLAY`, opened `/dev/dri/card1`
via a real libseat session, initialized libinput, advertised a Wayland
socket, and rendered a frame that scanned out correctly via KMS
page-flip - confirmed by screendumping the guest's virtual framebuffer
and matching the exact clear color (`[0.05, 0.05, 0.08]`) the compositor
renders. No client-side visual check yet (the VM has no Wayland-native
client installed to test against, only X11 ones - see below). No
hotplug (connectors or GPUs) after startup.
- ✅ XWayland integration (`crates/wayland/src/xwayland.rs`), udev/DRM
backend only (the winit backend would need its own `calloop::EventLoop`
added first - see the module's doc comment): spawns XWayland, starts
`X11Wm`, and implements `XwmHandler`/`XWaylandShellHandler` to bridge
X11-only clients into the same `WindowManager`/`Space` pipeline
xdg-shell windows use (`CreateNotify`/`MapRequest` create a real
`srdwm_core::Window`, matched by rules via `class()`; unmap/destroy
clean up the same way). **Verified live end-to-end**: an `xterm`
launched against the spawned XWayland renders as a correctly-sized,
server-managed window, and typing at it (via a real synthetic
QEMU-level keyboard, not a shortcut) reaches the shell inside it --
`ls` produced a new prompt line. Getting there surfaced three real bugs,
each root-caused with evidence rather than guessed at:
- XWayland tries `glamor` (GBM-based rendering) first; since this
compositor is deliberately software-only, glamor fails and previously
left XWayland on a rendering path that never used the
`xwayland_shell_v1` protocol at all (confirmed via `WAYLAND_DEBUG=1`
tracing: the global was bound but `get_xwayland_surface`/`set_serial`
were never called). Fixed by shadowing `Xwayland` on `PATH` with a
tiny wrapper script that always re-execs the real binary with `-shm`
- `smithay::xwayland::XWayland::spawn` builds its own fixed argument
list with no way to pass this directly, and its `XWaylandClientData`
has private fields so the spawn call itself can't be bypassed either.
- Even with `-shm`, `set_mapped(true)` was only ever called from inside
`finish_x11_window_setup`, itself gated on `X11Surface::wl_surface()`
already resolving - but XWayland doesn't appear to advance a window
past surface creation (no buffer attach, no further protocol traffic
at all) until the map is granted. A real deadlock, found by tracing
the *same* `WAYLAND_DEBUG=1` output before and after the `-shm` fix
and seeing identical behavior either way. Fixed by calling
`set_mapped(true)` unconditionally in `map_window_request`, before
checking whether `wl_surface()` is available.
- The window then rendered as a ~1px sliver: `map_window_request` seeded
the initial `srdwm_core::Window` geometry from
`X11Surface::geometry()`, which at `MapRequest` time can still be
whatever tiny default the X11 window was *created* with (our own
`configure_request` handler is deliberately a no-op - this compositor
owns layout for managed windows). Fixed by using the same fixed
800x600 default `new_managed_window`'s xdg-shell path already uses,
instead of trusting the client's initial size.
- Typing didn't reach the window at all until a fourth, broader bug was
found and fixed *outside* the XWayland code: nothing in the whole
Wayland backend ever called `KeyboardHandle::set_focus` - clicking a
window only updated `srdwm_core::WindowManager`'s own focus tracking,
never Wayland/X11 keyboard focus. This affected xdg-shell windows too,
not just XWayland ones. Fixed in `lib.rs`'s `handle_pointer_button`
(both the decoration-click and click-through-to-content-area paths,
the latter of which also never focused a window at all, only raised
it). `TitlebarHit::Close` was also X11-surface-blind (only called
`ToplevelSurface::send_close()`), fixed alongside.
- No font is installed in the test VM at all (a gap in the VM's package
set, not the code), so the titlebar band renders with no title text in
this environment - `decoration.rs`'s font-search fallback is working
exactly as designed; see its own section above for where actual text
rendering was verified.
- Not implemented: selections/clipboard, XSETTINGS, RandR
primary-output sync, override-redirect window geometry beyond initial
placement (all have harmless no-op default `XwmHandler` methods).
**Why the visual verification stopped short of a screenshot**: the winit
window opens on the *host* compositor, and the only available display in
this sandbox was the user's live desktop session (not an isolated nested
server the way Xephyr was for X11). A screenshot of that would have
captured the user's actual desktop/other work, which isn't appropriate to
casually paste into a build log. The X11 backend's Xephyr-based
verification is the same class of test, done on an isolated, disposable
display instead.
## QEMU VM verification
Both the X11 backend and the Wayland/DRM-udev backend were re-verified from
scratch in an isolated QEMU VM (not the sandbox they were originally built
in), to check they work somewhere other than the exact environment that
built them:
- **VM**: minimal Arch Linux rootfs (base, linux, xorg-server, xterm, mesa,
seatd, libinput, libxkbcommon, xf86-input-libinput - built by copying the
host's own already-installed files for these packages plus their full
dependency closure, rather than a fresh `pacstrap`, since this sandbox's
network throughput made a real package download impractical). Booted via
direct kernel+initramfs (no bootloader), `virtio-gpu`/`virtio-keyboard`/
`virtio-mouse`/`virtio-net`, autologin on both the serial console and
`tty1`, `-display none` with QMP `screendump` for visual verification
(no interactive GUI needed on the host side).
- **X11 backend**: `run-x11.sh` starts Xorg on `vt1` then execs `srdwm` as
an X11 client (the standard way it becomes the WM). Two `xterm`s spawned
via the config's `startup.lua` were reparented, tiled/placed by
`SmartPlacement`, and both show a drawn titlebar with real "xterm" title
text and close/maximize/minimize glyphs - screenshotted and visually
confirmed.
- **Wayland/DRM-udev backend**: run directly on the bare console (no `-x`
script needed - no `DISPLAY`/`WAYLAND_DISPLAY` set at all). Opened
`/dev/dri/card1` via `libseat`, initialized `libinput`, advertised a real
Wayland socket, and rendered/page-flipped a frame - confirmed by
screendumping the guest's virtual display and matching the exact clear
color the compositor renders. This required a real bug fix, found by
this exact test: `srdwm_platform::detect()` previously defaulted to X11
whenever neither `WAYLAND_DISPLAY` nor `XDG_SESSION_TYPE=wayland` was
set, *regardless of whether `DISPLAY` was set either* - meaning on a
genuinely bare TTY it picked X11, a backend that can never work there
(`srdwm_x11::X11Platform` only ever connects to an already-running X
server; it doesn't start one). `detect()` now only picks X11 when
`DISPLAY` is set without Wayland evidence; every other case, including a
bare TTY, resolves to Wayland, which is the only backend able to run
standalone there.
- **Not (yet) verified**: nested Wayland (the `backend_winit` path) running
as an X11 client under this VM's Xorg - `smithay`'s `winit` backend
failed with `Failed to initialize an event loop`, which is an error
surfaced from inside the `winit` crate's own X11 initialization, not
`srdwm`'s code; most likely this minimal VM's software-only Xorg is
missing a GLX/DRI3 piece `winit`'s EGL context creation wants. The nested
path was already verified once before (Xephyr-equivalent, log-verified
per the section above); this is a gap in re-verifying it in this
specific minimal VM, not a known-broken code path.
- No Wayland-native client was available in this minimal VM to visually
confirm client-side rendering under either Wayland backend (only
`xterm`, which is X11-only) - the compositor/socket/render-pipeline
side is confirmed, but no real Wayland app has been shown on-screen yet.
- **XWayland**: `xterm` launched with `DISPLAY` pointed at the udev
backend's spawned XWayland renders as a correctly-sized, decorated
(background-only in this VM - no font installed) window, and receives
real keyboard input end-to-end: a synthetic QEMU-level keypress sequence
(`ls` + Enter) executed inside the shell and produced a new prompt line,
screendump-confirmed. Getting there took four rounds of root-causing via
`WAYLAND_DEBUG=1` protocol tracing and fixing real bugs - see the
Wayland backend section above for the full account.
## Not implemented anywhere yet
- **`wlr-layer-shell-unstable-v1`** - `CompState` only delegates
`compositor`/`xdg_shell`/`xdg_decoration`/`shm`/`seat`/`output` (plus
`xwayland_shell` for XWayland). No layer-shell support means bars,
launchers (`wofi`, `rofi`'s layer-shell modes), notification daemons,
and lock-screen UIs can't run under srdwm at all - they don't create
`xdg_toplevel`s, they create layer surfaces. This is the single biggest
blocker to using srdwm-wayland as a real daily-driver session (e.g. for
an `ags`/`wofi`-based desktop).
- **`wl_data_device_manager`** (clipboard/drag-and-drop) - no global at
all is created for it, so copy-paste between two Wayland clients
doesn't work under srdwm yet (tools like `wl-copy`/`cliphist` have
nothing to talk to).
- **`ext-session-lock-v1`** (screen locking) - not implemented; a
lock-screen client (or a lid-switch/idle-triggered lock) has no
protocol to actually lock input/display under srdwm.
- **Multi-monitor** for the udev/DRM backend - deliberately scoped to a
single connector/output for the first pass (see `udev.rs`'s module
docs); no hotplug, no independent per-output layout.
- Animations (`general.animations`/`animation_duration` config keys exist
and are read into defaults, but nothing consumes them yet).
- A native GUI settings app (the legacy project's `GUI_SETTINGS.md` was
pure design doc even in C++; not revisited here).
|