Wayland workspace overview daemon

woven

Press a key, see all your workspaces and windows at once, click to focus.

Super+` overlay appears click a window focused
✓ Niri (primary) ✓ Hyprland ✓ Sway ✕ GNOME

What It Does

Woven is a Wayland overlay daemon for Niri. It provides a workspace overview, persistent bar with customizable widgets via Lua plugins, and a control center. Press a key to see all workspaces and windows at once, click to focus. The entire system is scriptable — write Lua plugins to add bar widgets, custom panels, and overlays without forking the project.

v2.5 complete: Full Lua API, persistent key-value store, HTTP client, plugin manager with GitHub distribution, cava visualizer, AI workspace namer, and theme presets. Everything is hot-reloadable without restarting the daemon.

Window Cards

Each window in the overview is a card showing its title. Hover a card to reveal action buttons:

ButtonAction
Close window
Toggle float
Toggle fullscreen
Toggle pin

Overview Controls

ActionResult
Click a window cardFocus that window, close overlay
Hover a window cardShow action buttons
Right-click / any keyClose overlay
ScrollScroll through workspaces

Compositor Support

CompositorStatusNotes
Niri✓ Primary targetFull support, designed for Niri
Hyprland✓ Full supportAlso supported, well-tested
Sway⚠ Basic supportWorks, some edge cases
GNOME✕ Not supportedDoes not implement wlr-layer-shell
KDE✕ Not supportedRequires different approach, out of scope

Project Structure

woven/
├── woven-sys/         main daemon — Lua VM, IPC server, compositor backend
├── woven-render/      render thread — Wayland surface, tiny-skia painter
├── woven-common/      shared types, IPC protocol, plugin API
├── woven-ctrl/        Iced GUI control panel + CLI
├── woven-protocols/   Wayland protocol bindings
├── woven-plugin/      plugin system crate
├── plugins/           bundled plugins (clock, battery, network, cava, etc.)
├── runtime/           Lua runtime files
├── config/            default Lua configs
└── get.sh             curl installer
PathContents
~/.config/woven/woven.luaYour main config
~/.config/woven/plugins/User-installed plugins (GitHub or local)
~/.config/woven/plugins-active.tomlPlugin state (managed by woven-ctrl)
/run/user/$UID/woven.sockIPC socket

Ecosystem

woven-shell is a companion project — a full Wayland shell suite (bar, lock screen, OSD, launcher, power menu, wallpaper, screenshot, workspace switcher). See the woven-shell documentation for details. The two products are independent but can work together as a unified desktop environment.

Installation

Quick Install

The fastest path. Downloads a prebuilt release, extracts it, and copies everything to the right places.

curl -fsSL https://raw.githubusercontent.com/viewerofall/woven/main/get.sh | bash

get.sh handles: binaries to ~/.local/bin/, runtime + config to ~/.config/woven/, service to ~/.config/systemd/user/, enables the service.

Make sure ~/.local/bin is in your $PATH. Add export PATH="$HOME/.local/bin:$PATH" to your shell rc if not.

Manual Install — From Release Archive

Download comp.tar.gz from the releases page, then:

tar -xzf comp.tar.gz
cp woven.service ~/.config/systemd/user/
cp -r runtime ~/.config/woven/
cp woven.lua ~/.config/woven/
cargo build --release
cp target/release/woven ~/.local/bin/
cp target/release/woven-ctrl ~/.local/bin/

Manual Install — From Git

git clone https://github.com/viewerofall/woven.git && cd woven
mkdir -p ~/.config/woven
cp -r runtime ~/.config/woven/
cp woven.lua ~/.config/woven/
cp woven.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable woven.service
cargo build --release
cp target/release/woven ~/.local/bin/
cp target/release/woven-ctrl ~/.local/bin/

Shell Environment

Woven needs WOVEN_ROOT set to find its config and runtime files:

sudo touch /etc/profile.d/woven.sh
echo 'export WOVEN_ROOT="$HOME/.config/woven"' | sudo tee -a /etc/profile.d/woven.sh
source /etc/profile.d/woven.sh

First-Time Setup

On first launch, if no config exists, Woven opens the setup wizard automatically. It handles compositor detection, theme selection, and keybind instructions — no terminal interaction required. You can also run it manually:

woven-ctrl --setup

Compositor Keybind Setup

Hyprland

exec-once = woven
bind = SUPER, grave, exec, woven-ctrl --toggle

Niri

spawn-at-startup "woven"

binds {
    Super+Grave { spawn "woven-ctrl" "--toggle"; }
}
Woven is designed for Niri. All v2.5 work was done on Niri. Performance is solid.

Sway

exec woven
bindsym Super+grave exec woven-ctrl --toggle
⚠ Sway support is implemented but untested.

Verify

systemctl --user status woven
woven-ctrl --toggle   # should open the overlay

Uninstall

systemctl --user stop woven && systemctl --user disable woven
rm ~/.local/bin/woven ~/.local/bin/woven-ctrl
rm -rf ~/.config/woven
rm ~/.config/systemd/user/woven.service
systemctl --user daemon-reload

Configuration

Config lives at ~/.config/woven/woven.lua. Edit directly or open woven-ctrl for the GUI editor. Apply changes without restarting:

woven-ctrl --reload

woven.theme()

Controls all visual properties of the overlay.

woven.theme({
    background    = "#1e1e2e",
    border        = "#6c7086",
    text          = "#cdd6f4",
    accent        = "#cba6f7",
    border_radius = 12,
    font          = "JetBrainsMono Nerd Font",
    font_size     = 13,
    opacity       = 0.92,
})
KeyTypeDescription
backgroundhex stringOverlay background color
borderhex stringWindow card border color
texthex stringWindow title text color
accenthex stringFocused window / active highlight
border_radiusnumberCard corner radius in pixels
fontstringFont name — match exact fc-list output
font_sizenumberFont size in points
opacityfloat 0–1Overall overlay opacity
Built-in presets selectable in woven-ctrl without editing woven.lua: Catppuccin Mocha, Dracula, Nord, Tokyo Night, Gruvbox

Lua Plugin API

Woven is fully scriptable. Load custom Lua modules from ~/.config/woven/plugins/ to extend the overlay and bar.

Core APIs

APIPurpose
woven.bar_widget(name, fn)Register a bar widget (text, updates on interval)
woven.panel(name, fn)Register a panel (complex UI in control center)
woven.overlay(name, fn)Register an overlay surface (fullscreen or floating)
woven.store.get(key)Retrieve value from persistent KV store
woven.store.set(key, value)Store value that survives hot-reloads and restarts
woven.http.get(url)Sync HTTP GET request, returns body or nil
woven.namer(text)AI workspace namer (local, no network calls)
woven.on_error(fn)Handle plugin errors with toasts and logging

Plugin Example: Custom Bar Widget

-- ~/.config/woven/plugins/hello.lua
local M = {}

function M.setup(config)
  woven.bar_widget("hello", function()
    return {
      text = "Hello, World!",
      update_interval_ms = 5000,
      on_click = function()
        -- handle click
      end
    }
  end)
end

return M

Plugin Installation

Use woven-ctrl Plugins tab to browse, download, and configure plugins from GitHub. Or copy .lua files to ~/.config/woven/plugins/ manually. Enable/disable via woven-ctrl — changes are saved to plugins-active.toml and hot-reloaded on next --reload.

woven.workspaces()

woven.workspaces({
    show_empty = false,
    min_width  = 200,
    max_width  = 400,
})
KeyTypeDefaultDescription
show_emptyboolfalseShow workspaces with no windows
min_widthnumber200Min workspace column width (px)
max_widthnumber400Max workspace column width (px)

woven.settings()

woven.settings({
    scroll_dir      = "horizontal",
    overlay_opacity = 0.92,
})
KeyTypeDefaultDescription
scroll_dirstring"horizontal""horizontal" or "vertical"
overlay_opacityfloat 0–10.92Overlay opacity

woven.animations()

woven.animations({
    overlay_open  = { curve = "ease_out_cubic",    duration_ms = 180 },
    overlay_close = { curve = "ease_in_cubic",     duration_ms = 120 },
    scroll        = { curve = "ease_in_out_cubic", duration_ms = 200 },
})

Available Curves

ValueDescription
linearConstant speed
ease_out_cubicFast start, slow end — good for opening
ease_in_cubicSlow start, fast end — good for closing
ease_in_out_cubicSlow at both ends — good for scrolling
springPhysics-based spring motion

Full Default Config

woven.theme({
    background    = "#1e1e2e",
    border        = "#6c7086",
    text          = "#cdd6f4",
    accent        = "#cba6f7",
    border_radius = 12,
    font          = "JetBrainsMono Nerd Font",
    font_size     = 13,
    opacity       = 0.92,
})

woven.workspaces({
    show_empty = false,
    min_width  = 200,
    max_width  = 400,
})

woven.settings({
    scroll_dir      = "horizontal",
    overlay_opacity = 0.92,
})

woven.animations({
    overlay_open  = { curve = "ease_out_cubic",    duration_ms = 180 },
    overlay_close = { curve = "ease_in_cubic",     duration_ms = 120 },
    scroll        = { curve = "ease_in_out_cubic", duration_ms = 200 },
})

Example: Deep Purple Theme

woven.theme({
    background    = "#0a0010",
    border        = "#331144",
    text          = "#d0c0ff",
    accent        = "#c792ea",
    border_radius = 8,
    font          = "JetBrainsMono Nerd Font",
    font_size     = 12,
    opacity       = 0.95,
})

woven.workspaces({ show_empty = false, min_width = 220, max_width = 380 })

woven.settings({ scroll_dir = "horizontal" })

woven.animations({
    overlay_open  = { curve = "spring",            duration_ms = 200 },
    overlay_close = { curve = "ease_in_cubic",     duration_ms = 100 },
    scroll        = { curve = "ease_in_out_cubic", duration_ms = 180 },
})

woven-ctrl

woven-ctrl is both a GUI control panel and a CLI tool. Run it bare for the GUI, pass flags to drive it from the terminal or compositor keybinds.

GUI Mode

woven-ctrl

Opens the graphical control panel where you can edit your theme visually, switch presets, use the built-in Lua editor, toggle the overlay, and run the setup wizard.

CLI Reference

CommandDescription
woven-ctrlOpen the GUI control panel
woven-ctrl --toggleToggle overlay open/closed
woven-ctrl --showForce overlay open
woven-ctrl --hideForce overlay closed
woven-ctrl --reloadReload woven.lua from disk
woven-ctrl --setupRun the first-time setup wizard

--toggle

The primary keybind command. Bind this in your compositor config. See Installation for examples.

--show / --hide

Force a specific state regardless of current visibility. Useful in scripts where you need predictable state rather than a toggle.

--reload

Hot-reloads woven.lua without restarting the daemon or recreating the Wayland surface. The built-in editor in GUI mode calls this internally when you save.

--setup

Runs the first-time setup wizard. Handles compositor auto-detection, color theme selection, and keybind instructions. Also runs automatically on first launch if no config is found.

IPC Socket

woven-ctrl communicates with the daemon via:

/run/user/<UID>/woven.sock

If the socket isn't found (daemon not running), CLI commands will fail with a clear error.

Scripting Examples

Hide on game launch (Steam)

woven-ctrl --hide; %command%; woven-ctrl --show

Auto-reload on config save

while inotifywait -e close_write ~/.config/woven/woven.lua; do
  woven-ctrl --reload
done

Check daemon status

if systemctl --user is-active --quiet woven; then
  echo "woven is running"
fi

Architecture

How Woven works internally. Useful for contributors, debuggers, or anyone who wants to understand what's running.

Crate Layout

CrateRole
woven-sysMain process: Lua VM, IPC server, compositor backend
woven-renderRender thread: Wayland surface, painter, input handling
woven-commonShared types and IPC protocol definitions
woven-ctrlIced GUI + CLI control panel

Daemon Lifecycle

systemd --user starts woven (woven.service)
  └─ woven-sys initializes
  └─ Lua VM loads ~/.config/woven/woven.lua
  └─ IPC socket created at /run/user/$UID/woven.sock
  └─ Compositor backend connects
  └─ daemon idles, waiting for IPC commands

woven-ctrl --toggle received
  └─ if overlay hidden → woven-render shows surface, queries
                         compositor, renders workspace cards
  └─ if overlay visible → surface hidden

Wayland Surface

A single fullscreen zwlr_layer_shell_v1 surface kept alive between uses:

layer:          TOP
anchor:         TOP | BOTTOM | LEFT | RIGHT  (fullscreen)
exclusive_zone: -1  (overlay — doesn't push other surfaces)
keyboard:       exclusive while visible, None when hidden

Show/hide uses attach(None)+commit rather than destroying and recreating the surface, avoiding protocol re-negotiation and visual glitches on toggle.

Compositor Backends

Auto-detected on startup by checking environment variables. Each backend implements the same internal trait — woven-render doesn't need to know which compositor it's talking to.

CompositorDetectionIPC mechanism
Hyprland$HYPRLAND_INSTANCE_SIGNATUREHyprland socket
Niri$NIRI_SOCKETNiri IPC socket
Sway$SWAYSOCKSway IPC socket

Lua VM

The Lua runtime (mlua, Lua 5.4) runs in woven-sys on a dedicated thread. It loads woven.lua and all plugins from ~/.config/woven/plugins/ on startup and on --reload. Plugins are live objects — bar widgets register themselves and update on intervals, panels expose config forms, overlays can be triggered via IPC. The Lua VM also manages:

Render Pipeline

When the overlay opens:

1. Query compositor backend → Vec<Workspace { windows: Vec<Window> }>
2. Compute layout: workspace columns, card positions
3. tiny-skia renders to back buffer:
   - background fill with opacity
   - workspace column separators
   - window cards (border, title, accent on focused window)
4. attach buffer → commit → overlay visible
5. Input loop:
   click       → focus command to compositor → close overlay
   scroll      → repaint
   key / RMB   → close overlay

IPC Protocol

Newline-delimited text over Unix socket:

CLIENT → SERVER:  "toggle\n"
SERVER → CLIENT:  "ok\n"

CLIENT → SERVER:  "reload\n"
SERVER → CLIENT:  "ok\n"  |  "err:<message>\n"

Key Dependencies

CratePurpose
smithay-client-toolkitWayland client abstractions
wayland-clientRaw Wayland protocol bindings
wayland-protocolszwlr_layer_shell_v1, screencopy, xdg protocols
mluaLua 5.4 runtime with async support
tiny-skiaSoftware rasterizer (painter backend)
fontdueFont rasterization
icedwoven-ctrl GUI framework
tokioAsync runtime (IPC server, Lua thread)
ureqSync HTTP client (woven.http.get)
serde_jsonJSON serialization (store, config)

Troubleshooting

Overlay doesn't appear on --toggle

Check the daemon is running:

systemctl --user status woven

If not running:

systemctl --user start woven

Check the IPC socket exists:

ls /run/user/$(id -u)/woven.sock

If not there, the daemon crashed. Check logs:

journalctl --user -u woven -n 50

Check WOVEN_ROOT is set:

echo $WOVEN_ROOT
# expected: /home/<you>/.config/woven

Daemon starts then immediately exits

Check logs for the reason:

journalctl --user -u woven -n 50

Common causes: Lua syntax error in woven.lua (line number shown), compositor not detected (check the relevant env var is set), or ~/.config/woven/runtime/ missing.

Lua error on startup / reload

The error message shows the file and line number. Common mistakes: missing comma between table fields, unclosed string, mismatched braces. Validate before reloading:

luac -p ~/.config/woven/woven.lua

Overlay appears but is completely blank

The compositor backend query is failing. Woven can't get workspace/window data.

Hyprland: check $HYPRLAND_INSTANCE_SIGNATURE is set and the socket exists at /tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket.sock.

Niri: run niri msg workspaces — if it fails, Niri IPC isn't working.

Performance on Niri

Niri is the primary target and performs well in v2.5+. If you experience sluggish overlay response, check that your Lua config isn't heavy (expensive computations on open) and verify that Niri IPC is responsive:

niri msg workspaces

This should return instantly. If slow, check Niri's load and system resources.

Overlay doesn't close on keypress

Another layer-shell surface may be grabbing exclusive keyboard focus. Check that no bar or other surface is set to keyboard = exclusive. Workaround: bind woven-ctrl --hide to a key explicitly.

Font not rendering / showing boxes

Check the exact font name:

fc-list | grep -i "jetbrains"

Use that exact string in woven.lua — font names are case sensitive and must match fc-list output exactly.

woven-ctrl GUI won't open

Check woven-ctrl is in PATH (which woven-ctrl) and that $WAYLAND_DISPLAY is set (echo $WAYLAND_DISPLAY).

Getting Help

File an issue at github.com/viewerofall/woven/issues. Include: Woven version, compositor + version, journalctl --user -u woven -n 50 output, your woven.lua.

Roadmap

v2.5 — Complete

All core Lua APIs and plugin systems shipped as of April 2026:

v2.5+ Quality of Life — Planned

Small, high-impact features coming soon:

v3 — Ecosystem Unification — TBD

Bind woven and woven-shell together as one coherent desktop environment:

v4 — DE Completeness — Future

Final pieces for a production-grade Wayland DE layer:

Not Planned