A desktop live wallpaper engine for Arch Linux
Inspired by KLWP (Android) — build interactive, animated wallpapers with formulas, data providers, and widgets.
- 8 layer types — text, shape, image, progress bar, font icon, music visualizer, stack, overlap
- Formula engine — embed live data in any property:
$df(HH:mm)$,$mi(artist)$,$bi(level)$ - 10 data providers — date/time, battery, music (MPRIS), system info, CPU/RAM, network, traffic, weather, Hyprland workspaces, AI image generation
- 8 animation types with 6 triggers — fade, rotate, scale, translate, color, blur, jiggle, flash
- Interactive wallpaper — click actions, scroll gestures (volume/brightness), hover animations
- 21 pre-built widgets — clocks, weather, music players, system monitors, full themes
- Wayland native — renders as a true wallpaper layer via
gtk-layer-shellon Hyprland
Coming soon
Required:
gtk3 webkit2gtk-4.1 gtk-layer-shell wireplumber
Optional:
brightnessctl # brightness scroll gestures
hyprland # primary compositor (Wayland)
sway # basic compositor support
Build tools:
rust cargo nodejs pnpm pkg-config
git clone https://github.com/jaslrobinson/lava.git
cd lava
./install.shOr manually:
pnpm install
cargo build -p lava-wallpaper --release
npx tauri build --bundles none
sudo install -Dm755 target/release/lava /usr/local/bin/lava
sudo install -Dm755 target/release/lava-wallpaper /usr/local/bin/lava-wallpaper# Using an AUR helper
yay -S lavalava- Launch LAVA — the editor opens with a blank project
- Add a widget — click the
+button in the toolbar, go to the Widgets tab, and click a preset (try "Minimal Clock") - Edit properties — select a layer in the left panel, adjust properties on the right (position, color, font, formulas)
- Apply as wallpaper — click the wallpaper button (picture frame icon) in the toolbar
- Save your theme — click Save to export as
.lavafile - Or Use a basic theme in the + icon to help get you started — click + go to widgets and then themes choose desktop dashboard or centered focus.
| Button | Action |
|---|---|
| + | Add layers, widgets, or full themes |
| Copy / Paste | Duplicate selected layer |
| Undo / Redo | History (Ctrl+Z / Ctrl+Y) |
| Interactive | Toggle live formula/animation preview in editor |
| Debug | Show layer bounds overlay |
| Wallpaper | Start/stop live wallpaper mode |
| Import | Load .rock or .komp components |
| Save / Open / New | Project file management |
| Tab | Purpose |
|---|---|
| Layers | Layer tree with drag-and-drop reordering, visibility/lock toggles, rename, right-click export |
| Globals | Create and edit global variables (text, number, color, switch, list) |
| Shortcuts | Define keyboard shortcuts with actions |
| BG | Background color/image, resolution presets (1080p, 1440p, 4K) |
| Settings | App configuration (providers, weather, wallpaper behavior) |
Select any layer to edit its properties:
- Transform — position, size, rotation, opacity, scale, anchor point, visibility formula
- Actions — click action, scroll action
- Type-specific — text content/font/color, shape fill/stroke/skew, image source/scale mode, etc.
- Shadow — color, offset, blur radius (text, shapes, images)
- Animations — add/remove with visual FX Builder
| Shortcut | Action |
|---|---|
Ctrl+C |
Copy selected layer |
Ctrl+V |
Paste layer |
Ctrl+Z |
Undo |
Ctrl+Y |
Redo |
Delete |
Delete selected layer |
Escape |
Exit wallpaper preview |
Super+Escape |
Exit wallpaper (global, works from any app) |
Renders formatted text with formula support.
Properties: text, fontSize, fontFamily, color, textAlign, maxLines, lineSpacing, shadow
The text field accepts formulas: set it to $df(HH:mm)$ to display the current time, or $mi(title)$ - $mi(artist)$ for the current song.
Rectangle, circle, oval, triangle, or arc with fill, stroke, and perspective transforms.
Properties: shapeKind, fill, stroke, strokeWidth, cornerRadius, skewX, skewY, shadow
Set fill to transparent or none for stroke-only shapes. Use skewX/skewY for perspective effects.
Displays local files, URLs, or formula-driven sources.
Properties: src, scaleMode (fit/fill/crop/stretch), cornerRadius, tint, shadow
The src field accepts file paths, URLs, or formulas like $mi(cover)$ for album art.
Arc, bar, or circle progress indicators driven by formulas.
Properties: style, min, max, value, color, trackColor, strokeWidth
Example: set value to $bi(level)$ for a battery meter, or $mu(div, rm(cpuuse), 1)$ for CPU usage.
Material Icons or Font Awesome glyphs, or custom SVG/PNG icons.
Properties: iconSet, glyphCode, color, fontSize, iconSrc
Use the built-in icon picker to browse thousands of icons, or search Iconify for more.
Real-time audio visualizer that captures system audio via PipeWire.
Properties: vizStyle (bars/wave), barCount (4-64), barSpacing, sensitivity, colorTop, colorMid, colorBottom, peakColor
Arranges child layers horizontally or vertically with spacing.
Properties: orientation, spacing
Free-form container where children are positioned independently by their x/y coordinates.
Formulas use $...$ delimiters and can be placed in any text or numeric property. Inside a formula, nested function calls don't need extra $ delimiters.
$df(HH:mm)$ → "14:05"
$df(EEEE, MMMM d)$ → "Wednesday, March 12"
$df(hh:mm a)$ → "02:05 PM"
$dp(h)$ → 14 (hour as number)
$tu(s)$ → 1710273600 (unix timestamp)
Format codes: HH (24h), hh (12h), mm, ss, a/A (am/pm), d/dd, M/MM/MMM/MMMM, EEEE/EEE (weekday), yyyy/yy, DDD (day of year), w (week)
$mu(round, 3.7)$ → 4
$mu(round, 3.14159, 2)$ → 3.14
$mu(ceil, rm(cpuuse))$ → CPU % rounded up
$mu(rnd, 1, 100)$ → random number 1-100
$mu(add, 5, 3)$ → 8
$mu(pow, 2, 10)$ → 1024
$mu(sqrt, 144)$ → 12
$mu(min, 50, bi(level))$ → smaller of 50 or battery
Functions: ceil, floor, round, abs, sin, cos, tan, asin, acos, atan, log, ln, pow, sqrt, min, max, rnd, add, sub, mul, div, mod, h2d, d2h
$tc(low, "HELLO")$ → "hello"
$tc(up, "hello")$ → "HELLO"
$tc(cap, "hello world")$ → "Hello World"
$tc(cut, "abcdef", 0, 3)$ → "abc"
$tc(ell, mi(title), 20)$ → "Long Song Titl..." (max 20 chars)
$tc(len, "hello")$ → 5
$tc(reg, "abc123", "[0-9]", "")$ → "abc"
$tc(n2w, 42)$ → "forty two"
$tc(ord, 3)$ → "3rd"
$tc(roman, 2024)$ → "MMXXIV"
$tc(json, gv(data), "results.0.name")$ → JSON path extraction
Functions: low, up, cap, cut, ell, split, len, count, lines, reg, n2w, ord, utf, roman, url, html, json
$if(bi(level)<20, LOW, OK)$
$if(mi(state)=PLAYING, ▶, ⏸)$
$if(bi(plugged)=1, Charging, bi(level)%)$
For loops:
$fl(1, 5, 1, ★, )$ → "★★★★★"
$ce(#ff0000, alpha, 50)$ → 50% transparent red
$ce(#ff0000, sat, 50)$ → desaturated red
$ce(#ff0000, lum, 80)$ → lighter red
$ce(#ff0000, comp)$ → complementary color
$ce(#ff0000, invert)$ → inverted
$cm(200, 80, 50)$ → create from HSL
$gv(myVar)$ → value of global variable "myVar"
Global variables can contain formulas themselves — they're evaluated recursively.
$wg("https://api.example.com/data", json, "results.0.name")$
$wg("https://example.com/feed.xml", rss, "title", 0)$
$wg("https://example.com/page", txt, "", 0)$ → first line
Results are cached with a configurable TTL (default 300 seconds).
$lrc(gv(ly_raw), mi(pos))$ → current lyric line
$lrc(gv(ly_raw), mi(pos), 1)$ → next line
$lrc(gv(ly_raw), mi(pos), -1)$ → previous line
Access live system data in any formula using $prefix(key)$.
| Key | Value |
|---|---|
epoch |
Unix timestamp |
h, hh, H, HH |
Hour (12h/24h, with/without padding) |
m, mm, s, ss |
Minute, second |
a, A |
am/pm, AM/PM |
d, dd, M, MM |
Day, month |
EEEE, EEE |
Weekday name (full/abbreviated) |
| Key | Value |
|---|---|
level |
0–100 |
status |
CHARGING, DISCHARGING, FULL |
plugged |
0 or 1 |
| Key | Value |
|---|---|
title, artist, album |
Track metadata |
state |
PLAYING, PAUSED, STOPPED |
pos, len |
Position/length in seconds |
percent |
Playback progress 0–100 |
cover |
Album art URL |
package |
Player name (e.g., spotify) |
| Key | Value |
|---|---|
model |
Hostname |
man |
Linux distribution |
build |
Kernel version |
boot |
Uptime (formatted) |
uptime |
Uptime (seconds) |
| Key | Value |
|---|---|
cpuuse |
CPU usage % |
memtot, memfree, memuse |
Memory (bytes) |
ramp |
RAM usage % |
sdtot, sdfree |
Disk space (bytes) |
| Key | Value |
|---|---|
connected |
0 or 1 |
name, ssid |
Connection/WiFi name |
strength |
Signal strength |
ip |
IP address |
| Key | Value |
|---|---|
srx, stx |
Download/upload speed (bytes/s) |
trx, ttx |
Total received/transmitted (bytes) |
| Key | Value |
|---|---|
temp, tempc, flik |
Temperature, feels-like |
hum, press |
Humidity, pressure |
cond, icon |
Condition text, icon code |
wspeed, wdir, wgust |
Wind |
Requires an API key in Settings. Supports OpenWeatherMap.
| Key | Value |
|---|---|
workspace |
Active workspace number |
ws_N_exists, ws_N_active, ws_N_windows |
Per-workspace state (N = 1-10) |
focused_app, focused_title |
Active window |
app_count |
Total open windows |
gpu |
GPU utilization % |
| Key | Value |
|---|---|
artistImage |
File path to generated image |
status |
idle, generating, ready, error, no api key, no music |
Generates AI art via Google Gemini based on the current music artist. Requires a Gemini API key at ~/.config/lava/gemini_api_key. Customize the prompt via the ai_prompt global variable.
Add animations to any layer via the Animations section in the Property Panel, or use the visual FX Builder.
| Type | Effect |
|---|---|
| Fade | Animate opacity |
| Rotate | Spin (degrees) |
| Scale | Grow/shrink |
| Translate | Move (pixels) |
| Color | Tint overlay |
| Blur | Gaussian blur |
| Jiggle | Oscillating rotation |
| Flash | Bright pulse on trigger |
| Trigger | When it fires |
|---|---|
| Time | Continuous loop based on speed |
| Scroll | User scrolls (amount maps to scroll delta) |
| Reactive | Driven by a formula rule value |
| Tap | On click/tap |
| Show | When layer becomes visible |
| Hover | On mouse hover |
- Amount — intensity (degrees for rotate, pixels for translate, 0-255 for fade/color)
- Speed — duration in milliseconds
- Delay — wait before starting (ms)
- Easing — linear, ease-in, ease-out, ease-in-out, bounce, elastic
- Loop — none, restart, reverse
Assign actions to any layer's Click Action or Scroll Action property. Actions propagate up the layer tree — a child without an action inherits its parent's.
| Action | Example | Effect |
|---|---|---|
music:play-pause |
Toggle playback | |
music:next |
Next track | |
music:previous |
Previous track | |
app:command |
app:firefox |
Launch application |
url:address |
url:https://google.com |
Open in browser |
overlay:name |
overlay:settings_panel |
Toggle layer visibility |
set:var:value |
set:theme:dark |
Set global variable |
editor:show |
Bring editor to front |
| Action | Effect |
|---|---|
volume:adjust |
Adjust system volume (PipeWire/WirePlumber) |
brightness:adjust |
Adjust screen brightness (brightnessctl) |
Both show an on-screen display (OSD) with the current level.
Click the wallpaper button in the toolbar to apply your theme as a live desktop wallpaper.
LAVA spawns a separate lava-wallpaper process that creates a Wayland layer-shell surface at the bottom layer, covering your entire screen behind all windows. The wallpaper renders the same Svelte canvas with full formula evaluation, animations, and interactivity.
- Fade on focus — wallpaper dims when app windows are focused (configurable opacity in Settings)
- Auto-start — enable in Settings to automatically apply the last wallpaper on login
- System tray — start/stop wallpaper, show editor, or quit from the tray icon
- Exit — press
Super+Escapefrom anywhere, orEscapein the editor preview
| Compositor | Status |
|---|---|
| Hyprland | Full support (workspace tracking, window focus detection, fade-on-focus) |
| Sway | Basic support (wallpaper rendering) |
| X11 | Not supported |
| Extension | Type | Description |
|---|---|---|
.lava |
Theme | Full project (layers, globals, shortcuts, settings) |
.rock |
Component | Single layer tree with bundled globals |
Access via the Settings tab (gear icon) in the left panel.
| Setting | Default | Description |
|---|---|---|
| Close to tray | On | Minimize to tray instead of quitting |
| Start minimized | Off | Launch hidden (tray only) |
| Auto-start wallpaper | Off | Apply last wallpaper on launch + add to system autostart |
| Fade on focus | On | Dim wallpaper when windows are focused |
| Fade opacity | 30% | How much to dim (0% = invisible, 100% = no fade) |
| Formula refresh | 1000ms | How often formulas re-evaluate |
| Provider intervals | Varies | Per-provider polling frequency |
| Weather source | OpenWeatherMap | Weather API provider |
| Weather API key | — | Your API key for weather data |
| Weather location | — | City name or lat,lon |
| Web cache TTL | 300s | How long $wg()$ results are cached |
Config files are stored at ~/.config/lava/:
~/.config/lava/
settings.json # App settings
gemini_api_key # Google Gemini API key (plain text, one line)
Cache and generated files at ~/.cache/lava/:
~/.cache/lava/
ai-images/ # Generated AI artist portraits
# Clone
git clone https://github.com/jaslrobinson/lava.git
cd lava
# Install dependencies
pnpm install
# Development mode (hot reload)
cargo build -p lava-wallpaper && pnpm tauri dev
# Release build
cargo build -p lava-wallpaper --release
npx tauri build --bundles none
# Binaries at:
# target/release/lava
# target/release/lava-wallpaper- Frontend: Svelte 5, TypeScript, HTML Canvas
- Backend: Rust (Tauri v2)
- Wallpaper: GTK3 + WebKitGTK + gtk-layer-shell
- Formula engine: Custom Rust crate (
kustom-formula) - Audio capture: PipeWire via
parec+ FFT
Inspired by KLWP (Kustom Live Wallpaper) for Android by Frank Monza. LAVA is an independent project — not affiliated with Kustom Industries.
MIT