srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/docs/ARCHITECTURE.md
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-04-02 00:58:00 +0200
committersrdusr <[email protected]>2024-04-02 00:58:00 +0200
commit8110bb2773b6c841029a51eca7971f42a36f480c (patch)
treeb3b364a6c0231fe408229e94dc9e25ce4562ad02 /docs/ARCHITECTURE.md
parentd1f856edc516047575cf46f12fad2b59006363c8 (diff)
downloadsrdwm-8110bb2773b6c841029a51eca7971f42a36f480c.tar.gz
srdwm-8110bb2773b6c841029a51eca7971f42a36f480c.zip
Rewrite srdwm in Rust: working X11 and Wayland backends, Lua config
The C++ prototype (moved to legacy-cpp/) was mostly a design skeleton: X11 and Windows backends were partially real, Wayland created the wlroots object graph but never wired a single event listener, macOS was stub except monitor enumeration, and the Lua engine's srd.bind() stored a key-combo string but never the actual closure. See docs/PRIOR_ART.md for the full audit. This replaces it with a Cargo workspace: - srdwm-core: platform-independent window/workspace/monitor state, a real master-stack tiling layout, and SmartPlacement grid/cascade/ snap-to-edge placement - fixing several bugs in the C++ version (hardcoded 2-column grid, cascade that never cascaded, snap-to-edge that always returned a fixed rect). 35 unit tests. - srdwm-config: the srd Lua API via mlua, implementing the surface docs/DEFAULTS.md always documented but the C++ engine never actually built (srd.window.close()/focus(direction), srd.workspace.next(), real keybinding closures, require("srd") support). 10 unit tests. - srdwm-x11: a real reparenting WM with a drawn title bar (buttons, drag, resize), verified live under Xephyr - frame placement and client offset match srdwm-core's computed geometry exactly, and the decoration renders correctly on screen. - srdwm-wayland: a from-scratch smithay compositor (the C++ version had nothing working to port from) - runs via the winit backend, tracks xdg-shell toplevels through the same WindowManager and hit-testing code X11 uses, verified to start/render/run without crashing. Decorations are solid-color (no text yet); see docs/IMPLEMENTATION_STATUS.md for exact scope. - srdwm-windows / srdwm-macos: structured, cfg-gated designs informed by komorebi/glazewm and yabai/AeroSpace respectively (see docs/PRIOR_ART.md), honestly marked as unbuilt/unverified since this sandbox has no Windows or macOS target.
Diffstat (limited to 'docs/ARCHITECTURE.md')
-rw-r--r--docs/ARCHITECTURE.md118
1 files changed, 118 insertions, 0 deletions
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
new file mode 100644
index 0000000..61d036c
--- /dev/null
+++ b/docs/ARCHITECTURE.md
@@ -0,0 +1,118 @@
+# Architecture
+
+## Crate layout
+
+```
+crates/
+ core/ srdwm-core window/workspace/monitor state, layout engine,
+ smart placement, hit-testing - pure logic,
+ no I/O, no platform dependency
+ config/ srdwm-config Lua (`srd`) scripting engine, wraps mlua
+ platform/ srdwm-platform the `Platform` trait + PlatformKind detection
+ x11/ srdwm-x11 X11 backend (x11rb)
+ wayland/ srdwm-wayland Wayland backend (smithay)
+ windows/ srdwm-windows Windows backend (windows-rs, cfg-gated)
+ macos/ srdwm-macos macOS backend (core-graphics/accessibility-sys, cfg-gated)
+ srdwm/ srdwm (bin) wires config + core + platform together
+```
+
+Dependency direction is strictly one-way: `core` depends on nothing else in
+the workspace; `platform` depends only on `core`; each backend depends on
+`core` + `platform`; `config` depends only on `core` (it never talks to a
+platform directly - see below); the `srdwm` binary is the only crate that
+depends on everything.
+
+## Why config never touches a platform directly
+
+`srdwm-config`'s `srd.window.close()` (etc.) mutates a
+`Rc<RefCell<WindowManager>>` shared with the running backend - it does not
+call into `srdwm-x11` or `srdwm-wayland` itself. The backend is the thing
+that notices the `WindowManager`'s state changed (on its next `poll_events`
+tick, via `main.rs`'s `sync()` helper) and pushes the resulting geometry to
+the real X11/Wayland surface.
+
+This indirection is deliberate: it's what let the exact same `srd` API
+implementation, with the exact same test suite, work correctly against a
+`WindowManager` in isolation (10 config tests never touch a display server)
+and then, unmodified, drive a real X11 session and a real Wayland
+compositor. If `srd.window.close()` called `platform.close()` directly, the
+config crate would need a generic `Platform` handle and every test would
+need a fake one.
+
+## The `Platform` trait
+
+```rust
+trait Platform {
+ fn kind(&self) -> PlatformKind;
+ fn poll_events(&mut self) -> Result<Vec<srdwm_core::Event>>;
+ fn monitors(&mut self) -> Result<Vec<Monitor>>;
+ fn apply_geometry(&mut self, window: WindowId, geometry: Rect) -> Result<()>;
+ fn set_title(&mut self, ...) / focus / minimize / restore / close (...);
+ fn set_decorated / set_border_color / set_border_width / redraw_decoration (...);
+ fn grab_keyboard(&mut self) / ungrab_keyboard(&mut self);
+}
+```
+
+`poll_events` is the one place each backend bridges its native event model
+(X11's blocking `XNextEvent`, Wayland's callback-driven `wl_display`
+dispatch) into the common `srdwm_core::Event` queue; everything downstream
+of that - layout, placement, focus, drag/resize - is platform-independent.
+This mirrors the legacy C++ `Platform` interface's shape (see
+`docs/PRIOR_ART.md`), which was one of the few architectural decisions in
+that codebase that held up.
+
+Both `X11Platform` and `WaylandPlatform` additionally hold their own
+`Rc<RefCell<WindowManager>>` clone (shared with `srdwm-config`), so that
+when a backend detects a new window (X11 `MapRequest`, Wayland
+`new_toplevel`), it can call `wm.alloc_window_id()` + `wm.add_window(...)`
+directly rather than needing a separate "please allocate an ID for me"
+round-trip through `main.rs`.
+
+## Decoration strategy per platform
+
+Windows can't be decorated the same way on every platform, so each backend
+takes the approach that's actually available to it (informed by the prior
+art in `docs/PRIOR_ART.md`):
+
+- **X11**: classic reparenting WM. srdwm creates a frame window, reparents
+ the client into it below a drawn titlebar band, and owns all decoration
+ pixels directly (Xlib/xcb core drawing). Full control, which is why "full
+ title bar support" (buttons, drag, resize, matching Windows/macOS) is most
+ complete here.
+- **Wayland**: srdwm *is* the compositor, so it negotiates
+ `zxdg_decoration_manager_v1` server-side mode and renders a decoration
+ band itself via `smithay`'s `SolidColorRenderElement`, composited above
+ each client surface. Same `ResizeEdge::hit_test` as X11; see
+ `docs/IMPLEMENTATION_STATUS.md` for what's not finished (text, precise
+ global-keybinding routing).
+- **Windows**: DWM will not give you a custom-width or custom-drawn frame
+ without disabling the native one entirely, so the design (not yet built --
+ see status doc) keeps DWM's frame and controls it (`DWMWA_BORDER_COLOR`),
+ matching how komorebi/glazewm operate rather than fighting DWM.
+- **macOS**: there is no public API to draw on another process's window at
+ all. The design (also not yet built) is a separate, click-through overlay
+ window that tracks the target window's position via the Accessibility
+ API, matching AeroSpace's AX-only approach rather than yabai's
+ SIP-disabling private APIs.
+
+## Why `srdwm_core::window::ResizeEdge::hit_test` is shared, not duplicated
+
+Titlebar hit-testing (which pixel band is "drag", which is the close
+button, which edge is a resize grab) is pure geometry - it doesn't need to
+know anything about X11 or Wayland. Putting it in `srdwm-core` means the
+X11 and Wayland backends *cannot* drift into behaving differently for the
+same click, which was worth the small indirection cost (both backends pass
+`(frame_rect, x, y)` in and get back a `TitlebarHit` enum to act on).
+
+## Config loading
+
+`Engine::new(wm, config_dir)` seeds every key documented in
+`docs/DEFAULTS.md` before any user script runs (so `srd.get(...)` never
+returns `nil` for a documented key), then `Engine::load_init()` executes
+`config_dir/init.lua`, which in the shipped example
+(`config/srd/init.lua`) calls `srd.load("keybindings")` etc. to pull in the
+rest - `srd.load(name)` reads and executes `config_dir/{name}.lua` in the
+same Lua state, so later files can see earlier `srd.bind()`/`srd.set()`
+calls. Config directory resolution order: `$SRDWM_CONFIG_PATH`, then
+`$XDG_CONFIG_HOME/srdwm/srd`, then `~/.config/srdwm/srd` (matching
+`docs/DEFAULTS.md`'s documented location).