woven
Press a key, see all your workspaces and windows at once, click to focus.
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:
| Button | Action |
|---|---|
| ✕ | Close window |
| ⧉ | Toggle float |
| ⊞ | Toggle fullscreen |
| ⬡ | Toggle pin |
Overview Controls
| Action | Result |
|---|---|
| Click a window card | Focus that window, close overlay |
| Hover a window card | Show action buttons |
| Right-click / any key | Close overlay |
| Scroll | Scroll through workspaces |
Compositor Support
| Compositor | Status | Notes |
|---|---|---|
| Niri | ✓ Primary target | Full support, designed for Niri |
| Hyprland | ✓ Full support | Also supported, well-tested |
| Sway | ⚠ Basic support | Works, some edge cases |
| GNOME | ✕ Not supported | Does not implement wlr-layer-shell |
| KDE | ✕ Not supported | Requires 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
| Path | Contents |
|---|---|
~/.config/woven/woven.lua | Your main config |
~/.config/woven/plugins/ | User-installed plugins (GitHub or local) |
~/.config/woven/plugins-active.toml | Plugin state (managed by woven-ctrl) |
/run/user/$UID/woven.sock | IPC 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.
~/.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"; }
}
Sway
exec woven
bindsym Super+grave exec woven-ctrl --toggle
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,
})
| Key | Type | Description |
|---|---|---|
background | hex string | Overlay background color |
border | hex string | Window card border color |
text | hex string | Window title text color |
accent | hex string | Focused window / active highlight |
border_radius | number | Card corner radius in pixels |
font | string | Font name — match exact fc-list output |
font_size | number | Font size in points |
opacity | float 0–1 | Overall overlay opacity |
Lua Plugin API
Woven is fully scriptable. Load custom Lua modules from ~/.config/woven/plugins/ to extend the overlay and bar.
Core APIs
| API | Purpose |
|---|---|
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,
})
| Key | Type | Default | Description |
|---|---|---|---|
show_empty | bool | false | Show workspaces with no windows |
min_width | number | 200 | Min workspace column width (px) |
max_width | number | 400 | Max workspace column width (px) |
woven.settings()
woven.settings({
scroll_dir = "horizontal",
overlay_opacity = 0.92,
})
| Key | Type | Default | Description |
|---|---|---|---|
scroll_dir | string | "horizontal" | "horizontal" or "vertical" |
overlay_opacity | float 0–1 | 0.92 | Overlay 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
| Value | Description |
|---|---|
linear | Constant speed |
ease_out_cubic | Fast start, slow end — good for opening |
ease_in_cubic | Slow start, fast end — good for closing |
ease_in_out_cubic | Slow at both ends — good for scrolling |
spring | Physics-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
| Command | Description |
|---|---|
woven-ctrl | Open the GUI control panel |
woven-ctrl --toggle | Toggle overlay open/closed |
woven-ctrl --show | Force overlay open |
woven-ctrl --hide | Force overlay closed |
woven-ctrl --reload | Reload woven.lua from disk |
woven-ctrl --setup | Run 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
| Crate | Role |
|---|---|
woven-sys | Main process: Lua VM, IPC server, compositor backend |
woven-render | Render thread: Wayland surface, painter, input handling |
woven-common | Shared types and IPC protocol definitions |
woven-ctrl | Iced 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.
| Compositor | Detection | IPC mechanism |
|---|---|---|
| Hyprland | $HYPRLAND_INSTANCE_SIGNATURE | Hyprland socket |
| Niri | $NIRI_SOCKET | Niri IPC socket |
| Sway | $SWAYSOCK | Sway 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:
woven.store— persistent KV store, survives reloads and restartswoven.http.get— sync HTTP client for plugins that need external data (weather, etc.)woven.namer— AI workspace namer (runs locally, no network)- Plugin discovery and hot-reload on
--reload
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
| Crate | Purpose |
|---|---|
smithay-client-toolkit | Wayland client abstractions |
wayland-client | Raw Wayland protocol bindings |
wayland-protocols | zwlr_layer_shell_v1, screencopy, xdg protocols |
mlua | Lua 5.4 runtime with async support |
tiny-skia | Software rasterizer (painter backend) |
fontdue | Font rasterization |
iced | woven-ctrl GUI framework |
tokio | Async runtime (IPC server, Lua thread) |
ureq | Sync HTTP client (woven.http.get) |
serde_json | JSON 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:
- Full Lua API:
woven.store(persistent KV),woven.http.get(sync HTTP),woven.namer(AI workspace naming) - Plugin System: bar widgets, panels, overlays. Load from GitHub or local. Plugin settings in woven-ctrl GUI
- Bundled Plugins: clock, battery, network, sysinfo, cava visualizer, nowplaying, launcher, date, greeting, app_rules, uptime, ws_logger
- Themes: Catppuccin, Dracula, Nord, Tokyo Night, Gruvbox presets
- Hot-reload:
woven-ctrl --reloadwithout restarting daemon - Screencopy integration: XRGB8888 little-endian support for window capture
v2.5+ Quality of Life — Planned
Small, high-impact features coming soon:
- Inline workspace rename: Click workspace name in overview, type, Enter to rename. Integrates with AI namer.
- Bar quick-strip: Right-click bar for volume/brightness sliders without opening full control center
- Window peek on hover: Hover window card in overview, screencopy thumbnail animates in
- Plugin quick-toggle: Right-click bar shows enabled plugins with one-click disable/enable
- Low battery indicator: Bar color shift or icon blinking below 15% battery
v3 — Ecosystem Unification — TBD
Bind woven and woven-shell together as one coherent desktop environment:
- Cross-socket awareness: woven and woven-shell discover each other via shared IPC protocol. Components are optional — woven works alone, woven-shell works alone, or both work together.
- Event bridge: battery low → plugin callback, media change → nowplaying update, workspace change → shell bar update
- Shared config: Single
~/.config/woven/init.luafor theme, bar, shell behavior — loaded by both products - woven-notify: Notification daemon implementing
org.freedesktop.NotificationsDBus spec - woven.ipc Lua API: Plugins can emit events:
woven.ipc.emit("battery.low", { pct = 12 })
v4 — DE Completeness — Future
Final pieces for a production-grade Wayland DE layer:
- xdg-desktop-portal backend: Implements file picker, screen share, screenshot delegation. Lets apps use woven's stack correctly.
- woven-data daemon: Centralized system state poller (battery, network, CPU, media). Plugins and shell components subscribe instead of polling independently.
- Deep Niri integration: Workspace events, window focus, fullscreen state piped to Lua and shell components
Not Planned
- GNOME / KDE: No
wlr-layer-shell, require fundamentally different approach — out of scope - macOS / Windows: Out of scope