A real perspective world
xdg-shell clients become lit 3D panes with rounded glass frames, depth, wobble and shadows. Drawing, picking and collision share one geometry.
Wayland, with depth
Shady turns ordinary Wayland windows into objects in a shared 3D world — and keeps a conventional scene-graph path for the days you just need to work.
What's inside
The compositor owns Wayland objects, GPU resources, lifetime and validation. Plugins and scripts own the effects, layouts and simulations layered on top, and can be swapped while it runs.
xdg-shell clients become lit 3D panes with rounded glass frames, depth, wobble and shadows. Drawing, picking and collision share one geometry.
Window representations, render hooks with the live camera, input and per-window shaders. ABI v2 migrates state across reloads.
config.lua shapes the compositor before modules resolve; init.lua reacts to windows, workspaces and keys at runtime.
Drive input, timers and screenshots from Lua against a headless GLES2 backend, under AddressSanitizer and UBSan.
--safe drops the spatial renderer for a conventional wlroots scene graph, so a broken experiment never locks you out.
shady-shell adds a taskbar, workspaces, launcher, window menu and quick settings over its own protocol, themeable per rice.
Rices
A rice is a config, a Lua runtime layer and a few native plugins. Two ship with the repository; both run nested or straight from a VT.
Make it yours
Everything above is built from these two layers. The same Lua runtime drives the headless test suite, and plugins never touch raw GL state.
-- config.lua — shape the compositor
shady.set("window_corner_radius", 14.0)
shady.set("window_border_focus_color", "#FFB070")
shady.set("floor_horizon_fog", 1.0)
shady.plugin("build/libshady-plugin-afterglow.so")
-- init.lua — react to the desktop
shady.bind("Super+Return", function()
shady.spawn("foot")
end)
shady.on("window.mapped", function(window)
shady.log("arrived: " .. window.app_id)
end)/* A sky that knows where the camera is. */
static void draw(shady_host host,
const struct shady_render_context *ctx, void *data) {
if (!SHADY_RENDER_CONTEXT_HAS(ctx, aspect)) return;
api->shader_uniform_vec4(host, sky, "u_cam_fwd",
ctx->camera_forward[0], ctx->camera_forward[1],
ctx->camera_forward[2], 0.f);
api->shader_uniform_float(host, sky, "u_time",
ctx->time_seconds);
api->shader_draw_fullscreen(host, sky);
}
hook = api->render_hook_add(host,
SHADY_RENDER_STAGE_AFTER_BACKGROUND, draw, NULL);Fast path
The Nix shell pins wlroots and the whole toolchain. Start nested inside your current session, then move to a real VT once the basics are solid.
nix develop
./tools/build.sh
# nested, with the Afterglow rice
./rices/afterglow/run.sh