Added README
This commit is contained in:
parent
40cb952c26
commit
3e1c412fea
3 changed files with 757 additions and 0 deletions
420
GUIDE.md
Normal file
420
GUIDE.md
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
# Build a Rust Tile Manager, Step by Step
|
||||
|
||||
This is a small, Codecrafters-style roadmap for building a Wayland desktop tile manager in Rust, in the same general family as Hyprland or niri.
|
||||
|
||||
The simplest practical path is:
|
||||
|
||||
1. Build a Wayland compositor, not an X11 window manager.
|
||||
2. Use `smithay` as the foundation.
|
||||
3. Start with one monitor, one keyboard, one pointer, one tiled layout.
|
||||
4. Add polish only after mapping, focus, input, and rendering work.
|
||||
|
||||
## What You Are Actually Building
|
||||
|
||||
At a high level, your app needs to do five jobs:
|
||||
|
||||
1. Start a Wayland server and accept client connections.
|
||||
2. Track outputs, seats, windows, focus, and layout state.
|
||||
3. Process input events from keyboard and pointer devices.
|
||||
4. Render client surfaces to one or more outputs.
|
||||
5. Apply a tiling policy when windows are created, resized, focused, or closed.
|
||||
|
||||
If you want something "like Hyprland or niri", that does not mean cloning their code structure. It means building these capabilities in order:
|
||||
|
||||
1. Working compositor loop.
|
||||
2. Window mapping and focus.
|
||||
3. Basic tiling.
|
||||
4. Multi-monitor support.
|
||||
5. Animations, gestures, rules, and IPC.
|
||||
|
||||
## Direct Crates You Will Likely Add
|
||||
|
||||
These are the direct dependencies worth adding to your own `Cargo.toml`.
|
||||
|
||||
### Core crates
|
||||
|
||||
- `smithay`
|
||||
What it does: the main compositor toolkit. It provides Wayland protocol helpers, backend abstractions, input handling, output handling, rendering helpers, and optional XWayland support.
|
||||
- `calloop`
|
||||
What it does: callback-based event loop. Smithay is designed around it.
|
||||
- `tracing`
|
||||
What it does: structured logging. Smithay uses it internally, so using it yourself keeps diagnostics consistent.
|
||||
- `tracing-subscriber`
|
||||
What it does: prints and filters `tracing` logs.
|
||||
- `anyhow`
|
||||
What it does: fast application-level error handling while the project is still evolving.
|
||||
- `thiserror`
|
||||
What it does: typed errors for your own subsystems once the codebase grows.
|
||||
- `bitflags`
|
||||
What it does: ergonomic flag types for key modifiers, window state, output capabilities, and internal options.
|
||||
- `xkbcommon`
|
||||
What it does: keyboard layout and keymap handling through `libxkbcommon`.
|
||||
|
||||
### Optional but common
|
||||
|
||||
- `serde`
|
||||
What it does: config file parsing and IPC payloads.
|
||||
- `toml`
|
||||
What it does: TOML config parsing.
|
||||
- `clap`
|
||||
What it does: command-line flags like `--config`, `--socket`, `--debug`.
|
||||
- `tracing-appender`
|
||||
What it does: file logging.
|
||||
|
||||
### Optional later-stage crates
|
||||
|
||||
- `smithay` with XWayland-related features
|
||||
What it does: lets X11 apps run through XWayland.
|
||||
- `zbus`
|
||||
What it does: DBus integration for desktop services.
|
||||
- `async-channel` or `crossbeam-channel`
|
||||
What it does: internal message passing if you split rendering, IPC, or config reload paths.
|
||||
|
||||
## Minimum `Cargo.toml` Shape
|
||||
|
||||
Use this as a starting point, then adjust feature flags to match the backend you choose:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
bitflags = "2"
|
||||
calloop = "0.14"
|
||||
smithay = "0.7"
|
||||
thiserror = "2"
|
||||
tracing = { version = "0.1", features = ["max_level_trace", "release_max_level_info"] }
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
||||
xkbcommon = "0.9"
|
||||
```
|
||||
|
||||
Important: `smithay` feature selection matters more than the exact crate list. You will eventually enable only the backends you are actually using.
|
||||
|
||||
## System Dependencies
|
||||
|
||||
Rust crates are not the whole story. A Wayland compositor also needs Linux graphics/input stack libraries installed on the system.
|
||||
|
||||
Expect to need some combination of:
|
||||
|
||||
- `libwayland`
|
||||
- `libxkbcommon`
|
||||
- `libudev`
|
||||
- `libinput`
|
||||
- `libdrm`
|
||||
- `gbm`
|
||||
- `egl` / `gles2`
|
||||
- `pixman`
|
||||
- `xwayland` if you want X11 app support
|
||||
|
||||
The exact set depends on whether you target:
|
||||
|
||||
- `winit` backend first
|
||||
Easier for learning and debugging.
|
||||
- `udev`/DRM backend
|
||||
Required for a real standalone desktop session.
|
||||
|
||||
## The Learning Order
|
||||
|
||||
Treat each stage as a checkpoint. Do not skip ahead.
|
||||
|
||||
### Stage 0: Pick the first target
|
||||
|
||||
Start with a nested compositor using Smithay's easier development path.
|
||||
|
||||
Goal:
|
||||
|
||||
- Run your compositor inside an existing desktop session.
|
||||
- Open a test client.
|
||||
- See logs and state changes.
|
||||
|
||||
Why:
|
||||
|
||||
- This avoids debugging KMS/DRM, seat management, and GPU initialization on day one.
|
||||
|
||||
### Stage 1: Boot the event loop
|
||||
|
||||
Build the smallest app that:
|
||||
|
||||
1. Initializes `tracing`.
|
||||
2. Creates a `calloop::EventLoop`.
|
||||
3. Creates Wayland display state.
|
||||
4. Registers the core Smithay globals you need.
|
||||
5. Enters the dispatch loop.
|
||||
|
||||
Deliverable:
|
||||
|
||||
- The process starts cleanly and stays alive.
|
||||
|
||||
### Stage 2: Accept clients and map surfaces
|
||||
|
||||
Add the core Wayland protocol pieces:
|
||||
|
||||
1. Compositor state.
|
||||
2. Shared-memory buffer support.
|
||||
3. XDG shell support.
|
||||
4. Seat support.
|
||||
|
||||
Track:
|
||||
|
||||
- New toplevel windows.
|
||||
- Window title and app id.
|
||||
- Configure/commit lifecycle.
|
||||
- Mapped vs unmapped state.
|
||||
|
||||
Deliverable:
|
||||
|
||||
- A client can connect and create a toplevel surface.
|
||||
|
||||
### Stage 3: Build your internal model
|
||||
|
||||
Before fancy rendering, define your own data structures.
|
||||
|
||||
You need types roughly like:
|
||||
|
||||
- `CompositorState`
|
||||
- `OutputState`
|
||||
- `SeatState`
|
||||
- `Workspace`
|
||||
- `TileTree` or `ColumnLayout`
|
||||
- `WindowId`
|
||||
- `ManagedWindow`
|
||||
|
||||
Store:
|
||||
|
||||
- Which workspace a window belongs to.
|
||||
- Whether the window is tiled, floating, fullscreen, or urgent.
|
||||
- Focus order.
|
||||
- Geometry requested by your layout engine.
|
||||
|
||||
Deliverable:
|
||||
|
||||
- You can print a stable snapshot of layout state after every window event.
|
||||
|
||||
### Stage 4: Render one output
|
||||
|
||||
Now connect your scene state to rendering.
|
||||
|
||||
Keep it simple:
|
||||
|
||||
1. Support one output first.
|
||||
2. Draw a background color.
|
||||
3. Render mapped client surfaces.
|
||||
4. Present frames continuously or when damaged.
|
||||
|
||||
Deliverable:
|
||||
|
||||
- One client window is visible on screen.
|
||||
|
||||
### Stage 5: Keyboard and pointer input
|
||||
|
||||
Wire up:
|
||||
|
||||
1. Pointer motion.
|
||||
2. Pointer button press.
|
||||
3. Keyboard key press.
|
||||
4. Modifiers through `xkbcommon`.
|
||||
|
||||
Implement:
|
||||
|
||||
- Focus follows click, or focus follows keyboard only.
|
||||
- A compositor modifier key.
|
||||
- At least three commands: spawn terminal, close focused window, cycle focus.
|
||||
|
||||
Deliverable:
|
||||
|
||||
- You can interact with a client and trigger compositor actions.
|
||||
|
||||
### Stage 6: First tiling layout
|
||||
|
||||
Do not start with dynamic animations or fancy trees. Start with one deterministic layout.
|
||||
|
||||
Best beginner layouts:
|
||||
|
||||
1. Master-stack.
|
||||
2. Equal vertical columns.
|
||||
3. Niri-style scrolling columns.
|
||||
|
||||
Recommended first choice:
|
||||
|
||||
- Equal vertical columns or a simple master-stack.
|
||||
|
||||
Rules:
|
||||
|
||||
1. When a new tiled window appears, insert it into the active workspace.
|
||||
2. Recompute all tile rectangles.
|
||||
3. Send configure events with the new size.
|
||||
4. Render using those computed rectangles.
|
||||
|
||||
Deliverable:
|
||||
|
||||
- Opening a second and third window retile the workspace correctly.
|
||||
|
||||
### Stage 7: Workspaces
|
||||
|
||||
Add:
|
||||
|
||||
1. Multiple workspaces per output.
|
||||
2. Active workspace switching.
|
||||
3. Move focused window to another workspace.
|
||||
|
||||
Deliverable:
|
||||
|
||||
- You can switch workspaces and preserve layout state independently.
|
||||
|
||||
### Stage 8: Multi-output support
|
||||
|
||||
Track:
|
||||
|
||||
- Connected outputs.
|
||||
- Per-output current workspace.
|
||||
- Output geometry and scale.
|
||||
|
||||
Implement:
|
||||
|
||||
- New windows land on the focused output.
|
||||
- Workspaces are attached either globally or per output.
|
||||
|
||||
Deliverable:
|
||||
|
||||
- Two monitors work without corrupting focus or layout state.
|
||||
|
||||
### Stage 9: Real standalone session
|
||||
|
||||
After the nested version works, move toward a real desktop session.
|
||||
|
||||
This is where backend complexity increases:
|
||||
|
||||
1. DRM/KMS
|
||||
2. GBM/EGL or another rendering path
|
||||
3. Libinput
|
||||
4. Session/seat handling
|
||||
5. VT switching
|
||||
|
||||
Deliverable:
|
||||
|
||||
- The compositor can run on a TTY as the main desktop session.
|
||||
|
||||
### Stage 10: Quality-of-life features
|
||||
|
||||
Only now add polish:
|
||||
|
||||
1. Config file.
|
||||
2. IPC socket.
|
||||
3. Window rules.
|
||||
4. Floating windows.
|
||||
5. Fullscreen.
|
||||
6. Pointer constraints.
|
||||
7. Layer-shell panels and wallpapers.
|
||||
8. Screencopy.
|
||||
9. Idle inhibit.
|
||||
10. Animations.
|
||||
|
||||
## A Good Project Structure
|
||||
|
||||
Once the prototype starts working, split it like this:
|
||||
|
||||
```text
|
||||
src/
|
||||
main.rs
|
||||
app.rs
|
||||
state.rs
|
||||
backend/
|
||||
mod.rs
|
||||
nested.rs
|
||||
tty.rs
|
||||
input/
|
||||
mod.rs
|
||||
keyboard.rs
|
||||
pointer.rs
|
||||
bindings.rs
|
||||
layout/
|
||||
mod.rs
|
||||
columns.rs
|
||||
master.rs
|
||||
shell/
|
||||
mod.rs
|
||||
xdg.rs
|
||||
window.rs
|
||||
render/
|
||||
mod.rs
|
||||
scene.rs
|
||||
workspace/
|
||||
mod.rs
|
||||
config/
|
||||
mod.rs
|
||||
```
|
||||
|
||||
The important rule is simple:
|
||||
|
||||
- Smithay-facing code should stay near the edges.
|
||||
- Your layout and workspace logic should be mostly your own plain Rust types.
|
||||
|
||||
## What Each Crate Does in the Architecture
|
||||
|
||||
Think of the crates like this:
|
||||
|
||||
- `smithay`: Wayland compositor plumbing and backend integration.
|
||||
- `calloop`: the main event loop that everything runs inside.
|
||||
- `xkbcommon`: keyboard interpretation and modifiers.
|
||||
- `tracing` + `tracing-subscriber`: logs, spans, filtering, debugging.
|
||||
- `anyhow`: ergonomic top-level errors during rapid iteration.
|
||||
- `thiserror`: precise library-style errors for your own modules.
|
||||
- `bitflags`: compact internal state flags.
|
||||
- `serde` + `toml`: config loading.
|
||||
- `clap`: startup options.
|
||||
- `zbus`: desktop integration later.
|
||||
|
||||
## Suggested Milestone Checklist
|
||||
|
||||
If you want a clean progression, build in this exact order:
|
||||
|
||||
1. Process boots and enters event loop.
|
||||
2. Wayland client connects.
|
||||
3. XDG toplevel appears.
|
||||
4. One window renders.
|
||||
5. Focus works.
|
||||
6. Keyboard shortcuts work.
|
||||
7. Two windows tile correctly.
|
||||
8. Workspaces work.
|
||||
9. Multi-output works.
|
||||
10. Standalone TTY session works.
|
||||
11. XWayland works.
|
||||
12. Config and IPC work.
|
||||
|
||||
## Practical Advice
|
||||
|
||||
- Build a nested compositor first. This is the highest-leverage simplification.
|
||||
- Keep layout logic pure. A layout engine should accept windows and output rectangles and return tile rectangles.
|
||||
- Add exactly one layout first.
|
||||
- Keep a strong debug log for every surface map, unmap, commit, focus change, output change, and configure.
|
||||
- Do not implement animations until you trust your state model.
|
||||
- Do not add XWayland until native Wayland windows are solid.
|
||||
|
||||
## Recommended References
|
||||
|
||||
These are the most relevant primary sources I used:
|
||||
|
||||
- Smithay crate docs: https://docs.rs/smithay/latest/smithay/
|
||||
- Smithay project docs: https://smithay.github.io/smithay/smithay/
|
||||
- Smithay `wayland` module docs: https://docs.rs/smithay/latest/smithay/wayland/
|
||||
- Smithay `xwayland` module docs: https://docs.rs/smithay/latest/smithay/xwayland/
|
||||
- `wayland_server` docs: https://smithay.github.io/wayland-rs/wayland_server/
|
||||
- `calloop` docs: https://docs.rs/calloop/latest/calloop/
|
||||
- `tracing` docs: https://docs.rs/crate/tracing/latest
|
||||
- `anyhow` docs: https://docs.rs/crate/anyhow/latest
|
||||
- `thiserror` docs: https://docs.rs/crate/thiserror/latest
|
||||
- `bitflags` docs: https://docs.rs/bitflags
|
||||
- `xkbcommon` docs: https://docs.rs/xkbcommon/latest/xkbcommon/
|
||||
|
||||
## Final Recommendation
|
||||
|
||||
If your goal is "a simple tile manager similar to Hyprland or niri", the best first version is:
|
||||
|
||||
1. Rust
|
||||
2. Wayland
|
||||
3. `smithay`
|
||||
4. `calloop`
|
||||
5. one output
|
||||
6. one workspace
|
||||
7. one tiling layout
|
||||
8. nested backend first
|
||||
|
||||
That path is realistic. Starting with standalone DRM, multi-monitor, XWayland, animations, and IPC all at once is not.
|
||||
Loading…
Add table
Add a link
Reference in a new issue