Skip to content
Internals

Internals

SamWM architecture, concurrency model, and memory management.

Architecture

cmd/samwm/main.go          Entry point
internal/compositor/       Core compositor loop
internal/config/           Configuration loading
internal/layout/           Layout algorithms
internal/window/           Window types and store
pkg/wlroots/               CGo wlroots bindings
pkg/lua/                   Lua scripting VM

Threading Model

SamWM runs the wlroots event loop on a single locked OS thread (required by wlroots). All C callbacks execute on this thread.

Main goroutine (locked OS thread):
├── wl_display_run()          ← blocks on Wayland events
│   ├── output frame callback
│   ├── keyboard events
│   ├── pointer events
│   ├── XDG surface events
│   └── ...
└── wl_display_destroy()

Signal goroutine:
├── SIGINT/SIGTERM handler
└── wl_display_terminate()    ← safe from any thread

Concurrency Safety

All mutable state uses sync.RWMutex:

  • Window: Each window has its own mutex for geometry, state flags, metadata
  • Store: Global window store with read-write lock
  • Compositor: RWMutex on workspace state, output state
  • Lua VM: RWMutex for window state cache, event handlers

Lock ordering (to prevent deadlocks):

  1. Compositor.mu
  2. Store.mu
  3. Window.mu

Memory Management

Allocation Patterns

  • Window slices: Pre-allocated with capacity hints (make([]*Window, 0, len))
  • Window store: map[int64]*Window — O(1) lookup, no slice scanning
  • Event handlers: Append-only slices, never shrunk
  • Layout queue: BFS queue nodes allocated on stack when possible

GC Pressure Reduction

  • sync.RWMutex over channels for state access (zero allocation reads)
  • sync.Pool not used because window objects are long-lived, not transient
  • Pre-sized slices in hot paths (layout algorithms, window iteration)
  • Atomic counters for window IDs (no mutex needed)

CGo Memory

  • All wlroots objects are Go structs wrapping C pointers
  • cgo.Handle used for callback routing (Go GC tracks handles)
  • ListenerManager cleans up all C signal handlers on shutdown
  • No manual C.malloc/C.free — all C objects owned by wlroots

Layout Engine

Layouts implement the layout.Layout interface:

type Layout interface {
    Name() string
    Apply(windows []*window.Window, bounds window.Rect, cfg config.Config)
}

Layouts receive window pointers and set geometry directly. The compositor then reads geometry and applies scene tree positions.

Layout Algorithm Patterns

Layout Algorithm Time Complexity
master-stack Two-pass partition O(n)
dwindle BFS split queue O(n)
bsp BFS split queue O(n)
columns Direct calculation O(n)
floating No-op O(1)
fullscreen Direct set O(n)

All layouts are O(n) — no nested loops, no sorting.

Workspace System

10 workspaces, each with:

  • Independent window list
  • Independent layout algorithm
  • Scene tree subtree (enabled/disabled for visibility)

Workspace switching:

  1. Disable old workspace scene tree
  2. Enable new workspace scene tree
  3. Re-apply layout
  4. Focus first window in new workspace

Shutdown Sequence

  1. Signal received → wl_display_terminate() (safe from any goroutine)
  2. wl_display_run() returns (on main thread)
  3. ListenerManager.RemoveAll() — disconnect all C signal handlers
  4. wl_display_destroy() — clean up wlroots resources

This ensures no callbacks fire after resources are freed.

Build

nix-shell --run "make build"
# or
nix-shell --run "go build -ldflags '-s -w' -o build/samwm ./cmd/samwm/"

Binary size: ~3.2MB (stripped).

Dependencies

  • wlroots 0.20 — Wayland compositor library (C, via CGo)
  • gopher-lua — Lua 5.1 VM (pure Go)
  • Go 1.22+ — Required for built-in min/max