Sway & wlroots Deep Dive¶
Sway is a drop-in i3 replacement built on wlroots, the compositor library that implements Wayland protocols so you don't have to. This article covers Sway's IPC, wlroots internals, WayVNC integration, and the xdg-shell configure dance -- all the moving parts Warmwind patches daily.
Sway Architecture¶
graph LR
IPC["IPC"] --> Tree["Tree Layout"]
Config["Config"] --> Tree
Tree --> SG["Scene Graph"]
Protos["Protocols"] --> SG
SG --> Renderer["Renderer"] --> Backend["Backend"]
Sway delegates all hardware interaction to wlroots. It focuses on window management (tree layout, workspaces, scratchpad) and configuration.
IPC Protocol¶
Sway exposes an i3-compatible IPC over $SWAYSOCK (Unix stream socket).
Every message is a 14-byte header + JSON payload:
# List all outputs
swaymsg -t get_outputs | jq '.[].name'
# Subscribe to window events (streaming JSON)
swaymsg -t subscribe '["window"]'
# Move focused container -- used by automation scripts
swaymsg move container to workspace 2
Warmwind uses IPC to programmatically launch Chromium on the correct output, set fullscreen, and monitor session state.
wlroots Backend / Renderer / Scene Graph¶
Backend (wlr_backend): Abstracts input/output hardware.
| Backend | Use case |
|---|---|
| DRM | Real hardware (GPU + displays) |
| Wayland | Compositor-in-compositor (testing) |
| Headless | No display -- WayVNC uses this |
| X11 | Development on X11 host |
Renderer (wlr_renderer): GPU abstraction for compositing.
- GLES2 (default), Vulkan (newer), Pixman (software fallback).
- Sway never calls GL directly; it goes through
wlr_renderer.
Scene graph (wlr_scene): Declarative tree of what to display.
wlr_scene_output_commit()renders the tree to an output.- Enables direct scanout: if one surface covers the entire output, skip compositing and scan out the client buffer directly.
WayVNC Integration¶
sequenceDiagram
participant Client as Chromium
participant Sway as Sway (wlroots)
participant VNC as WayVNC
participant Remote as VNC Viewer
Client->>Sway: wl_surface.attach(buffer)
Sway->>Sway: wlr_scene renders frame
VNC->>Sway: wlr-screencopy-v1: copy_output
Sway->>VNC: buffer with rendered frame
VNC->>VNC: Encode (Tight, ZRLE, raw)
VNC->>Remote: RFB protocol frame
Remote->>VNC: Key/pointer events
VNC->>Sway: wlr_virtual_keyboard / pointer
WayVNC uses wlr-screencopy-unstable-v1 to capture frames and
wlr-virtual-pointer / virtual-keyboard to inject input. It runs as a
regular Wayland client with protocol privileges.
WayVNC now also supports ext-image-copy-capture-v1, the standardized
successor to the deprecated wlr-screencopy protocol. New deployments should
prefer ext-image-copy-capture-v1; WayVNC falls back to wlr-screencopy on
older compositors.
Wayland objects ↔ Rust ownership
Smithay (Rust compositor library) solved the object-lifecycle problem that killed wlroots-rs. Wayland's create/destroy semantics map naturally to Rust's ownership model -- when the Rust struct drops, the protocol object is destroyed. No dangling references, no double-free, no manual ref-counting.
Expert insight
"wlroots is an open standards platform -- it implements the protocols and gets out of your way." -- Drew DeVault, wlroots creator
Minimal wlroots Compositor Skeleton¶
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xdg_shell.h>
struct server {
struct wl_display *display;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_scene *scene;
struct wlr_xdg_shell *xdg_shell;
struct wl_listener new_output;
struct wl_listener new_xdg_toplevel;
};
static void handle_new_output(struct wl_listener *listener, void *data) {
struct wlr_output *output = data;
wlr_output_init_render(output, server->allocator, server->renderer);
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state);
wlr_scene_output_create(server->scene, output);
}
int main(void) {
struct server server = {0};
server.display = wl_display_create();
server.backend = wlr_backend_autocreate(
wl_display_get_event_loop(server.display), NULL);
server.renderer = wlr_renderer_autocreate(server.backend);
server.allocator = wlr_allocator_autocreate(
server.backend, server.renderer);
server.scene = wlr_scene_create();
server.xdg_shell = wlr_xdg_shell_create(server.display, 6);
/* wire up listeners ... */
const char *socket = wl_display_add_socket_auto(server.display);
wlr_backend_start(server.backend);
setenv("WAYLAND_DISPLAY", socket, true);
wl_display_run(server.display); /* event loop */
return 0;
}
Build with Meson: meson setup build && ninja -C build.
xdg-shell Configure / Ack Dance¶
sequenceDiagram
participant C as Client
participant S as Compositor
S->>C: xdg_toplevel.configure(width, height, states[])
S->>C: xdg_surface.configure(serial)
C->>C: Resize buffer, redraw
C->>S: xdg_surface.ack_configure(serial)
C->>S: wl_surface.commit(new buffer)
- Compositor sends a configure event with suggested size and states (maximized, fullscreen, activated, tiled).
- Client must
ack_configurewith the serial before committing. - Until acked, the compositor uses the previous buffer geometry.
- Client can send its own preferred size via
set_min_size/set_max_size.
For kiosk mode, Sway sends configure(fullscreen) immediately. The client
must ack and provide a buffer matching the output resolution.
What's new (2025–2026)
- Sway 1.10 / 1.11 released with incremental protocol and scene-graph improvements. No Sway 2.0 is planned -- the project stays on evolutionary releases atop wlroots 0.18+.
- COSMIC desktop (System76, Smithay-based) shipped stable in Dec 2025 -- the first major Rust compositor to reach production.
- niri (Rust scrolling tiler) is daily-drivable, attracting users who want a column-based workflow instead of i3-style tiling.
Glossary
- wlroots
- Modular C library implementing Wayland compositor plumbing. Used by Sway, Wayfire, Cage.
- IPC (Inter-Process Communication)
- Sway's JSON-over-Unix-socket control protocol, compatible with i3's IPC.
- Scene graph
- wlroots tree structure of renderable nodes. Enables damage tracking and direct scanout.
- Direct scanout
- Bypassing compositing by presenting a client buffer directly to the display plane.
- wlr-screencopy
- Unstable Wayland protocol for capturing compositor output into a shared buffer.
- xdg-shell
- Stable Wayland protocol for desktop window management (toplevel, popup, positioner).
- configure serial
- Monotonic ID linking a compositor's configure event to the client's acknowledgment.