srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/docs/IMPLEMENTATION_STATUS.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/IMPLEMENTATION_STATUS.md')
-rw-r--r--docs/IMPLEMENTATION_STATUS.md223
1 files changed, 205 insertions, 18 deletions
diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md
index 354cd75..ae78cd0 100644
--- a/docs/IMPLEMENTATION_STATUS.md
+++ b/docs/IMPLEMENTATION_STATUS.md
@@ -99,6 +99,25 @@ 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:
+**Module layout.** `lib.rs` had grown to ~1260 lines holding state, every
+protocol handler, input routing and rendering; it is now a 78-line shell
+(module declarations plus `connect()`), with the rest split by
+responsibility:
+
+| module | responsibility |
+| --- | --- |
+| `state.rs` | `CompState` (the `Display<D>` state everything hangs off), outputs, window bookkeeping |
+| `protocols.rs` | smithay `*Handler` impls + `delegate_*!` macros - deliberately thin |
+| `input.rs` | keyboard/pointer routing and what "focus" means |
+| `lock.rs` | session lock as one feature: state, handler *and* its render helpers |
+| `screencopy.rs` | hand-written `wlr-screencopy` (no smithay helper exists) |
+| `winit.rs` / `udev.rs` | the two backends - all they differ in is how a frame reaches a screen |
+| `decoration.rs` / `xwayland.rs` | titlebar rasterisation; XWayland bridge |
+
+`lock.rs` is grouped by *feature* rather than by kind on purpose: the
+security-relevant invariant spans state, protocol handling and rendering at
+once, so splitting it across three files would have hidden it.
+
- ✅ 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
@@ -203,6 +222,181 @@ What's here is a genuine from-scratch `smithay`-based compositor, not a stub:
- Not implemented: selections/clipboard, XSETTINGS, RandR
primary-output sync, override-redirect window geometry beyond initial
placement (all have harmless no-op default `XwmHandler` methods).
+- ✅ **`wlr-layer-shell-unstable-v1`** (`WlrLayerShellHandler`, `delegate_layer_shell!`
+ in `lib.rs`): layer surfaces are mapped into the output's
+ `smithay::desktop::LayerMap` (`layer_map_for_output`), which `render_output`
+ renders automatically - no rendering-path changes were needed, only state
+ wiring, initial-configure-on-commit, and pointer/keyboard routing
+ (`layer_surface_under` in `lib.rs`, checked ahead of our own decorations and
+ xdg-shell windows so bars/launchers/notifications/lock UIs sit properly on
+ top; `Exclusive`-interactivity surfaces grab keyboard focus on commit,
+ `OnDemand` ones on click). Background/bottom-layer pointer routing (e.g. a
+ wallpaper daemon wanting clicks) is out of scope - nothing needed for the
+ daily-driver gate requires it.
+ **Verified live** against two real, unmodified clients (waybar 0.x, wofi
+ 1.5.3) run as actual Wayland clients of a running `srdwm` (winit backend,
+ `WAYLAND_DEBUG=1` protocol tracing): waybar's Top-layer bar configured
+ correctly ("Bar configured (width: 934, height: 45) for output:
+ srdwm-wayland"); wofi's Exclusive-interactivity launcher surface was
+ created, sized, and configured with no crash. Getting there surfaced and
+ fixed three real bugs:
+ - `Output::change_current_state` was called unconditionally every render
+ frame (60/s), which - harmless with zero Wayland-native clients ever
+ connected before this - floods any client actually bound to `wl_output`
+ with duplicate `mode`/`done` events forever. Fixed by only calling it
+ (and re-`arrange()`ing the layer map) when the output size actually
+ changed.
+ - The very first `configure` sent to a newly-mapped layer surface used
+ stale geometry: `map_layer`'s own `arrange()` runs before the client's
+ `set_size`/`set_anchor`/etc. requests (and the commit applying them) have
+ even arrived, so the initial `send_configure()` was re-sending that
+ stale pre-request computation instead of recomputing from what the
+ client actually asked for (caught live: wofi's `set_size(420, 550)` was
+ silently ignored, and it configured stuck at the output/2 fallback
+ instead). Fixed by re-`arrange()`ing on every layer-surface commit
+ (`LayerMap::arrange` only ever sends a configure when something actually
+ changed, so this is a no-op on unrelated commits).
+ - The missing `zxdg_output_manager_v1` (xdg-output) global - a separate,
+ real gap of its own, see below - made wofi's own layer-shell setup code
+ call a Wayland request on a proxy that was never bound (it doesn't
+ null-check), **segfaulting the client**, not just failing gracefully.
+ Root-caused with `gdb` (crash was `wl_proxy_marshal_constructor(proxy=0x0,
+ opcode=1, ...)`, matching `zxdg_output_manager_v1.get_xdg_output`) and
+ confirmed by comparing a `WAYLAND_DEBUG=1` trace of the same `wofi`
+ binary against the user's real Hyprland session (which advertises
+ xdg-output and doesn't crash it) side by side with the trace against
+ `srdwm`.
+- ✅ **xdg-output (`zxdg_output_manager_v1`)**: added via smithay's
+ `OutputManagerState::new_with_xdg_output`, piggybacking on the existing
+ `delegate_output!`/`OutputHandler` wiring (no new handler trait needed).
+ Not itself in the original "biggest blocker" list, but found to be a hard
+ requirement in practice while fixing layer-shell above - see the wofi
+ segfault account.
+- ✅ **Clipboard**: `wl_data_device_manager`, `zwp_primary_selection_v1`,
+ and `zwlr_data_control_manager_v1`, all three sharing smithay's single
+ `SelectionHandler`. Data-control is the one that matters most for this
+ user's session: `wl-paste --watch cliphist store` (in their Hyprland
+ autostart) needs to read the selection *without* holding keyboard focus,
+ which the core data-device protocol cannot do.
+ The non-obvious wiring is that selection focus must follow keyboard focus
+ - `set_keyboard_focus` now also calls `set_data_device_focus` and
+ `set_primary_focus`, because the data-device protocols only offer the
+ selection to, and accept `set_selection` from, the focus-holding client.
+ **Verified live** against the user's own tools: `wl-copy`/`wl-paste`
+ round-tripped both clipboard and primary; `wl-paste --watch cliphist
+ store` captured three successive copies; and a real `wezterm` toplevel
+ was observed receiving `wl_data_device.data_offer` + `selection` over
+ `WAYLAND_DEBUG=1`, i.e. the core (non-data-control) path works too.
+ Drag-and-drop uses smithay's default `ClientDndGrabHandler`/
+ `ServerDndGrabHandler` behaviour and has *not* been separately tested.
+ This surfaced a real pre-existing bug, fixed here: nothing ever gave a
+ **newly-created** window Wayland focus. `WindowManager::add_window` sets
+ its own `focused` field, but no code path turned that into a
+ `KeyboardHandle::set_focus`, so a freshly-opened app received no
+ keystrokes and could not paste until it was clicked. (Same class as the
+ click-to-focus bug fixed in the XWayland pass; this was the creation
+ path.)
+- ✅ **`ext-session-lock-v1`** (screen locking): `SessionLockHandler` with
+ per-output lock surfaces. `locked` gates both rendering (only the lock
+ surface, over an opaque black clear - no windows, decorations, or layer
+ surfaces) and input (all keys go to the lock surface, and **no key is
+ treated as a WM binding**, since the shipped config binds
+ `Mod4+Return` to spawn a terminal and honouring that at a locked screen
+ would defeat the lock entirely). The lock is confirmed only *after* a
+ client-content-free frame has actually been presented, never at request
+ time, so the locker is never told "the screen is safe" while the user's
+ windows are still on screen.
+ **Verified live** with a purpose-written minimal `ext-session-lock`
+ client (no locker - hyprlock/swaylock/etc. - is installed on this
+ machine, so there was nothing else to test against; the user's
+ `~/.scripts/lock` currently falls through to `loginctl lock-session`):
+ lock → cleared frame → `locked` confirmation → lock surface configured to
+ the real output size → `unlock_and_destroy` → normal operation restored.
+ Three properties were checked by counting protocol events delivered to a
+ real `wezterm` launched at each point:
+ - unlocked: 1 `wl_keyboard.enter` (control);
+ - locked: 0 `wl_keyboard.enter`, 0 `wl_pointer.enter`;
+ - locker killed *without* unlocking: still 0 - the session correctly
+ stays locked when the screen locker crashes, as the protocol requires.
+ The locked-case count was **1, not 0, before a bug was found and fixed by
+ this exact test**: `new_managed_window` called `set_keyboard_focus`
+ unconditionally, so merely opening a window at a locked screen handed it
+ keyboard focus. The guard now lives in `set_keyboard_focus` itself, as
+ the single chokepoint every focus path goes through.
+- ✅ **`wlr-screencopy-unstable-v1`** (`crates/wayland/src/screencopy.rs`):
+ what `grim` uses, and therefore what the user's `Print` / `Alt+Print`
+ binds (`grim`, `slurp | grim -g -`) and `wf-recorder` need. smithay 0.7
+ ships **no** helper for this protocol, so the `GlobalDispatch`/`Dispatch`
+ plumbing is written out by hand against the raw `wayland-protocols-wlr`
+ server bindings (a new direct dependency, pinned to the version smithay
+ already uses so both see one set of types). Capture is deferred: a `copy`
+ request only queues the frame, and pixels are read back during the render
+ pass via `ExportMem::copy_framebuffer`.
+ **Verified live with real `grim`**: full-output capture, region capture
+ (`-g "0,0 420x110"`, confirmed by screenshotting a window and reading the
+ PNG back - correct offset, size, colours, and orientation, which is also
+ what establishes that no `y_invert` flag is needed), and an
+ out-of-bounds region (clamped, no crash). While the session is locked,
+ queued captures are rejected outright rather than served or left
+ queued - confirmed: `grim` fails fast with "failed to copy output" and
+ writes nothing.
+ One real bug was found and fixed by this testing: reading back the winit
+ backend's **EGL window surface** destroyed the GL context on the first
+ capture (`eglSwapBuffers: BAD_SURFACE` → `BAD_ALLOC` → "context has been
+ lost", taking the whole compositor down), root-caused by A/B-ing the
+ identical build with only the readback call removed. The winit path now
+ renders a second pass into an offscreen `GlesRenderbuffer` and reads
+ *that*, costing an extra scene render only on frames where a capture was
+ actually requested. The udev/pixman path is unaffected - its render
+ target is already a plain memory image, so reading it directly is safe.
+ Not implemented: `linux_dmabuf` capture (the manager is capped at
+ protocol version 2 for that reason) and cursor overlay
+ (`overlay_cursor` is accepted and ignored - this backend draws no
+ cursor of its own yet).
+- ✅ **Multi-monitor** (udev/DRM backend). Every connected connector becomes
+ a `UdevHead` with its **own** scanout buffers, damage tracker and
+ page-flip state, laid out left-to-right in a shared global coordinate
+ space; the `PixmanRenderer` is shared, since they are one GPU. A head
+ whose flip is still in flight is skipped for that pass and resumes when
+ its own page-flip event arrives (matched by CRTC), so monitors at
+ different refresh rates each run at their own pace instead of the slowest
+ gating the rest.
+ Connector→CRTC assignment never reuses a CRTC, so a machine with more
+ monitors than CRTCs drives as many as the hardware allows and logs the
+ rest. Modes are chosen by the `PREFERRED` flag rather than list order.
+ The rest of the compositor reaches outputs through
+ `CompState::{primary_output, output_at, output_for_wl}` rather than a
+ single field, which is what kept the change small: layer surfaces map to
+ the output the client names, session lock creates **one lock surface per
+ output** (and only confirms the lock once *every* output has both a
+ surface and a presented frame - otherwise a second monitor could still
+ be showing the desktop when the locker is told the session is safe),
+ screencopy captures the output the client names, and the pointer is
+ clamped to the union of all heads so it can cross between them.
+ **Verified live in the QEMU VM** with a two-output `virtio-gpu`
+ (`max_outputs=2`, second connector forced on with `video=Virtual-2:...e`,
+ default VGA removed so the GPU choice is unambiguous):
+ - srdwm logged `2 connected output(s)` and built both heads
+ (`Virtual-1 1280x800 at x=0`, `Virtual-2 ... at x=1280`), and core saw
+ `2 monitor(s)`;
+ - QMP `screendump` of **both** heads returned each one's own resolution,
+ both filled with srdwm's exact clear colour `rgb(12,12,20)`
+ (= `[0.05, 0.05, 0.08]`) - i.e. both are really being rendered and
+ scanned out, not just enumerated;
+ - a window forced by `srd.rule` to **global** x=1500 appeared on head 1 at
+ head-local x=**220** (= 1500 − 1280, the exact translation) with its
+ srdwm titlebar, while head 0 stayed completely empty (0 of 64000 sampled
+ pixels differed from the clear colour).
+ The nested winit backend remains single-output by construction (it is one
+ window on a host compositor).
+
+**Known limitation of the nested (winit) backend**: it renders through the
+host compositor's frame callbacks, so if the srdwm window is occluded or on
+another workspace, the host stops scheduling it, `eglSwapBuffers` blocks,
+and srdwm's whole main loop stalls - it stays alive but stops serving
+clients until the window is visible again. Observed repeatedly while
+testing. This affects only the nested development path; the udev/DRM
+backend drives its own page flips and is unaffected.
**Why the visual verification stopped short of a screenshot**: the winit
window opens on the *host* compositor, and the only available display in
@@ -274,24 +468,17 @@ built them:
## 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.
+All three protocols originally identified as blocking srdwm-wayland from
+being a real daily-driver session (bars/launchers/notifications/lock UIs,
+clipboard, screen locking) are now implemented and verified - see the
+Wayland backend section above. What is left:
+
+- **Connector hotplug** - connectors are probed once at startup, so
+ plugging a monitor in (or unplugging one) while srdwm is running is not
+ noticed. Needs a udev event source, which this backend does not register
+ yet. Multi-monitor itself *is* implemented (see above); only hotplug is
+ missing.
+- **Multi-GPU** - only the primary GPU's connectors are driven.
- 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