Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
docs: update README and add compositor CLAUDE.md
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
CLAUDE.md | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
README.md | 56 ++++++++++++++++++++---
2 files changed, 203 insertions(+), 5 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..658d321
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,152 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## What this is
+
+`cce-fx` is a standalone Wayland compositor + tiling window manager written in Rust,
+built directly on **wlroots 0.20** (via FFI) and a vendored **scenefx** for
+blur/rounded-corner scene effects. It began as a Rust rewrite of the
+[river](https://isaacfreund.com/software/river) compositor — hence the GPL-3.0
+license, `SPDX-FileCopyrightText: © 2020 The River Developers` headers, and river
+protocol XML files you'll see throughout `src/server/` and `protocol/`.
+
+This crate lives inside a larger Cargo workspace (the workspace root is the **parent**
+directory `../Cargo.toml`, which lists ~20 `cce-*` sibling apps). This crate is the
+compositor; the siblings (`cce-status-interface`, `cce-system-settings`, etc.) are
+clients that talk to it over its sockets. The only intra-workspace dependency is
+`cce-ui` (`../cce-ui`).
+
+> Note: `README.md` is stale — it describes an old split `cce-server`/`cce-client`
+> architecture. The real architecture is monolithic (a single `cce-fx` server binary
+> plus the `ccectl` control client). `standalone client mode is deprecated`.
+
+## Build / run / install
+
+```sh
+make build # cargo build --release
+make run # cargo run --bin cce-fx
+make install # build + install to ~/.local/bin (see below)
+make clean # cargo clean
+```
+
+`make install` copies `cce-fx` (symlinked as `cce`), `ccectl`, the `scripts/*` menu
+helpers, and `gpu-watcher` (+ its systemd user unit) into `~/.local/bin`. It reads
+binaries from `../target/release/` because the workspace target dir is at the parent.
+
+Building emits a harmless warning that per-package `[profile.*]` in this `Cargo.toml`
+is ignored because profiles are only honored at the workspace root.
+
+### Two binaries
+
+- **`cce-fx`** (`src/bin/cce.rs`, symlinked to `cce`) — the compositor server.
+ Any arg other than `client`/`help` just starts the server (`cce_fx::run_server()`).
+- **`ccectl`** (`src/bin/ccectl.rs`) — thin IPC client; all logic is in
+ `src/cce_ctl.rs` (`run_cce_ctl`). Run `ccectl` with no args to see the full command
+ list (layout, view, mode, viewport, pointer-*, key*, bind, spawn, notify, exit, …).
+
+### System dependencies (checked by `build.rs`)
+
+Native libs via `pkg-config`: `wlroots-0.20`, `wayland-server`, `xkbcommon`,
+`pixman-1`, `libinput`, `libevdev`; linked directly: `GLESv2`, `EGL`, `drm`, `gbm`,
+`lcms2`. Also required at build time: `meson` + `ninja` (to compile the vendored
+`scenefx/` statically on first build), `wayland-scanner`, and the system
+`wayland-protocols` XML files under `/usr/share/wayland-protocols/`.
+
+## Tests
+
+Tests are sparse (unit tests in `slotmap.rs`, `config.rs`, `window_manager.rs`).
+The library crate name is `cce_fx` (underscored).
+
+```sh
+cargo test --lib # all library tests
+cargo test --lib <name> # single test by (substring) name
+cargo test --lib config:: # tests in the config module
+```
+
+## Build pipeline (`build.rs`)
+
+`build.rs` does a lot before Rust compiles:
+1. Runs `meson setup build` (first time) + `meson compile` inside `scenefx/`, static.
+2. Generates server headers for upstream protocols and header + `private-code` C for
+ the custom `river-*` / `cce-*` protocols (`protocol/`), using `wayland-scanner`.
+ `clean_xml` reorders files whose XML declaration follows a leading comment.
+3. Compiles `src/server/wlroots_log_wrapper.c` + the generated protocol `.c` files
+ into a static `wlroots_log_wrapper` lib.
+4. Runs `bindgen` over `wrapper.h` → `$OUT_DIR/bindings.rs`, blocklisting a handful of
+ types that are hand-defined `#[repr(C)]` in Rust instead.
+
+## Architecture
+
+Everything lives under `src/server/` and is re-exported flat from `src/lib.rs` via
+`#[path = ...]` module declarations. FFI-heavy: expect large `unsafe` blocks, raw
+pointers into wlroots C structs, and `wl_listener` callbacks throughout.
+
+### Key FFI idiom — `container_of!`
+
+`src/server/server.rs` defines the `container_of!` macro (the Rust equivalent of
+Zig's `@fieldParentPtr` / the C `wl_container_of`). wlroots delivers events through
+embedded `wl_listener` fields; callbacks use `container_of!(listener, Struct, field)`
+to recover the owning Rust struct from a listener pointer. Many wlroots structs are
+also redefined as hand-written `#[repr(C)]` mirrors in `server.rs` because bindgen
+treats them as opaque.
+
+### Central files (by size/importance)
+
+- **`server.rs`** — `Server` struct: owns the wlroots backend, renderer, `wl_display`,
+ xwayland, and all the manager sub-objects. `Server::init()` / `deinit()` wire up
+ every wlroots global. `run_server.rs` is the entry point: parses args, inits the
+ server, loads config + persisted state, adds the wayland socket, spawns the init
+ program (`~/.config/cce/init` via `sh -c`) and the IPC + status servers, then
+ `wl_display_run`.
+- **`window_manager.rs`** (~3800 lines) — the heart. Holds the WM state, viewport
+ pan/zoom ("overview"/desktop-grid), window lists, and the IPC command dispatcher
+ `process_ipc_command()`. IPC requests arrive on an mpsc channel drained by a
+ wlroots event-loop timer (`handle_ipc_timer`) so all mutation happens on the main
+ thread.
+- **`window.rs`** (~3300 lines) — per-window model and rendering (borders, blur,
+ viewport transforms).
+- **`tiling.rs`** — `TilingMode` enum: `Floating`, `Cascade`, `Grid`, `Fullscreen`,
+ `Popup`, `Overlay`, `Maximized`. Modes apply per-window and per-viewport.
+- Input stack: `input_manager.rs`, `seat.rs`, `cursor.rs`, `keyboard*.rs`,
+ `xkb_*.rs`, `libinput_*.rs`, `pointer_*.rs`, `tablet*.rs`, `text_input.rs`,
+ `input_relay.rs`/`input_popup.rs` (IME).
+- Shell/surface: `xdg_toplevel.rs`, `xdg_popup.rs`, `shell_surface.rs`,
+ `layer_shell.rs`, `xwayland_window.rs`, `xwayland_override_redirect.rs`,
+ `drag_icon.rs`, `wm_node.rs`.
+- Output: `output.rs`, `output_manager.rs`. Session: `lock_manager.rs`,
+ `idle_inhibit_manager.rs`. Rendering: `scene.rs`, `scene_node_data.rs`.
+
+### Config
+
+Loaded on startup from **`$XDG_CONFIG_HOME/cce/config.kdl`** (falls back to
+`~/.config/cce/config.kdl`). An adjacent `input.kdl` is merged in for key bindings and
+input settings. **The format is KDL** (via the `kdl` crate) despite some source
+comments in `config.rs` still saying "TOML" — trust `parse_kdl_config`, not the
+comments. `config.rs` maps parsed values onto `WindowManager` state (layout gaps,
+border/blur/desktop styling, keybindings → `Action`s, startup programs, output/display
+settings). Live reconfiguration comes in over IPC (`ccectl reload`, `bind`, `layout …`,
+`config-done`, etc.).
+
+Persistent window state is saved to **`~/.local/state/cce/state.json`**
+(`XDG_STATE_HOME/cce/state.json`) on shutdown and restored on start
+(`save_state` / `load_state` / `spawn_restored_windows`).
+
+### IPC & status sockets
+
+- **Control socket** `/tmp/cce-{WAYLAND_DISPLAY}.sock` (`ipc_server.rs`): line-oriented
+ request/reply over a Unix socket. `ccectl` / `cce_ctl.rs` is the client.
+- **Status socket** `/tmp/cce-status-{WAYLAND_DISPLAY}.sock` (`status_server.rs`): runs
+ on its own thread; a client sends one subscription line (`viewport`, `layout`,
+ `title`, or `modifiers`) and receives JSON/text lines on every change. This feeds the
+ status bar (`cce-status-interface`). The main loop pushes updates through a
+ `StatusSender` mpsc handle.
+
+## Conventions
+
+- This is systems FFI code: raw pointers, `unsafe`, and manual wlroots listener wiring
+ are the norm. When adding a wlroots event handler, follow the existing pattern —
+ embed a `wl_listener`, register it, and recover `self` with `container_of!`.
+- Keep river's SPDX/copyright headers on files that carry them.
+- `scratch/` and `scratch/*` (and the many `.png`/`.log`/`patch*.py` files in the
+ parent dir) are ad-hoc debugging artifacts, not part of the build.
diff --git a/README.md b/README.md
index 07f9818..44f1a70 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,54 @@
-# CCE (Computing Environment) Monorepo
+# cce-fx
-This repository contains both `cce-server` (the compositor/window manager server) and `cce-client` (the window manager client) integrated into a single Cargo workspace.
+`cce-fx` is a standalone Wayland compositor and tiling window manager written in Rust,
+built on **wlroots 0.20** (via FFI) with a vendored **scenefx** for blur and
+rounded-corner scene effects. It began as a Rust rewrite of the
+[river](https://isaacfreund.com/software/river) compositor and remains GPL-3.0 licensed.
-## Structure
+This crate is the compositor. It lives inside the larger `cce` Cargo workspace (root at
+the parent directory), alongside client apps such as `cce-status-interface`,
+`cce-system-settings`, and other `cce-*` siblings that connect to it over its sockets.
-- [cce-server](file:///home/lsgalante/Dropbox/Clear/cce/cce-server): Compositor and server-side logic (wlroots/Rust-rewrite).
-- [cce-client](file:///home/lsgalante/Dropbox/Clear/cce/cce-client): Client control, layout management, and workspace tiling.
+## Binaries
+
+- **`cce-fx`** (installed and symlinked as `cce`) — the compositor server. Running it
+ starts the monolithic compositor and window manager.
+- **`ccectl`** — the control client. It talks to a running server over the control
+ socket. Run `ccectl` with no arguments to list the available commands (layout, view,
+ mode, viewport, pointer/key injection, bind, spawn, notify, reload, exit, …).
+
+## Build & install
+
+```sh
+make build # cargo build --release
+make run # cargo run --bin cce-fx
+make install # build, then install to ~/.local/bin
+make clean # cargo clean
+```
+
+`make install` places `cce-fx` (symlinked as `cce`), `ccectl`, the `scripts/*` menu
+helpers, and `gpu-watcher` (plus its systemd user service) into `~/.local/bin`.
+
+### Build requirements
+
+The build (`build.rs`) compiles the vendored `scenefx/` with **meson**/**ninja** on
+first run, generates Wayland protocol code with **wayland-scanner**, and binds wlroots
+via **bindgen**. It requires these system packages (found through `pkg-config`):
+`wlroots-0.20`, `wayland-server`, `xkbcommon`, `pixman-1`, `libinput`, `libevdev`, and
+the GL/DRM stack (`GLESv2`, `EGL`, `drm`, `gbm`, `lcms2`), plus the system
+`wayland-protocols` XML definitions.
+
+## Configuration
+
+On startup the server loads **`$XDG_CONFIG_HOME/cce/config.kdl`** (default
+`~/.config/cce/config.kdl`), merging an adjacent `input.kdl` for key bindings and input
+settings. The config format is [KDL](https://kdl.dev). A startup script at
+`~/.config/cce/init` is run via `sh -c`. Window layout state is persisted to
+`~/.local/state/cce/state.json` on exit and restored on the next start.
+
+Configuration can also be changed live over the control socket with `ccectl`.
+
+## Development
+
+See [CLAUDE.md](CLAUDE.md) for a detailed tour of the architecture, the FFI
+conventions, the `build.rs` pipeline, and the IPC/status socket protocols.