Beta · 1.0.0-beta.1
veil: A wallpaper daemon for Wayland compositors.
veil shows a picture or a looping video on each monitor, can give every monitor a wallpaper of its own, pauses a video nobody can see, and brings back what each monitor showed after a restart.
It is written in Rust, plays videos with GStreamer, and is controlled from the command line, a configuration file, or a socket that scripts and status bars can use.
- Version
- 1.0.0-beta.1
- License
- PolyForm Strict
- Written in
- Rust
- Needs
- wlr-layer-shell
# draws on every monitor (black until a wallpaper is set)
veil daemon &
# a video or a picture; saved, and shown again after a restart
veil set --input ~/Videos/waves.mp4
# what each monitor shows
veil statusOnce veil is installed, in your Wayland session. To start it with your session, use its systemd user unit instead of veil daemon &.
Pictures and video, on every monitor.
Supported means implemented, covered by automated tests and checked in a compositor. Experimental means it works, with a limit you should know about.
Pictures
SupportedPNG, JPEG, WebP, BMP, TIFF, GIF and the other formats of the image crate. A picture is drawn at the monitor’s physical resolution, then the decoded copy is freed.
Video, looped
SupportedWithout audio, in any format the installed GStreamer plugins play. Checked: H.264, HEVC, VP8, VP9 and AV1, and ten containers.
Every monitor its own
SupportedEvery monitor, or the ones you name, each with a wallpaper of its own, and monitors plugged in later. A monitor keeps its wallpaper by name.
with a video, a monitor change is experimental
Pauses what nobody sees
SupportedBehind a fullscreen window a video keeps its frame on screen and decodes nothing. A locked screen or a monitor that is off stops it after two seconds.
sway · experimental on Hyprland
Remembers
SupportedThe wallpaper of each monitor is saved, and shown again after a restart. veil set --no-persist skips that.
Never a blank in between
SupportedA new wallpaper replaces the old one only once it is ready. A video takes over when its first frame is on screen; a file that cannot be shown leaves the old wallpaper alone.
HiDPI and fractional scaling
SupportedPictures are drawn at the monitor’s physical resolution. Checked pixel by pixel at scales 1, 1.25, 1.5 and 2.
Controlled from anywhere
Supportedset, clear, pause, resume, toggle, status, outputs, quit and ping, with stable exit statuses, plus a control socket for scripts and status bars.
Five ways to fit a picture to a monitor.
--mode says how a wallpaper is fitted to its monitor, for pictures and videos alike. The daemon’s --mode and --bg-color are also the defaults of veil set.
--mode fillCovers the whole monitor, keeping the aspect ratio. The overflow is cropped evenly from both sides. The default.
--mode fitThe whole picture is visible, keeping the aspect ratio. Bars of --bg-color fill the rest.
--mode stretchScaled to exactly the monitor’s size; the aspect ratio is not kept.
--mode centerNative size, one pixel per device pixel, centred. Cropped if larger than the monitor, surrounded by --bg-color if smaller.
--mode tileNative size, repeated from the top-left corner. Videos fall back to center.
One command for every monitor, or for one.
Run the commands on two sample monitors. What the monitors show and what veil status prints are an illustration, set in the format veil uses.
Samples: ~/Pictures/sunset.jpg, ~/Pictures/dawn.jpg and the video ~/Videos/sunrise.mp4.
veil 1.0.0-beta.1 (pid 4218), running for 7s; videos pause on: fullscreen default: ~/Pictures/sunset.jpg (fill, #000000) DP-3: playing ~/Videos/sunrise.mp4 (fill, #000000); 1920x1080 at scale 1; video 1920x1080, 25 fps HDMI-A-1: showing ~/Pictures/sunset.jpg (fill, #000000); 2048x1152 at scale 1.25
A video nobody can see costs next to nothing.
Under Hyprland and sway, veil follows the compositor’s IPC and pauses a video nobody can see. On any compositor, a video whose frames the compositor does not take is stopped and its pipeline freed.
- CPU of the daemon
- 19.5 %
- veil status says
- playing
The video plays and decodes every frame.
- CPU of the daemon
- ≈ 0 %
- veil status says
- paused (hidden by a window)
Hidden for a second, the video keeps its frame on screen and decodes nothing. It plays on from the same frame when it can be seen again.
- CPU of the daemon
- ≈ 0 %
- veil status says
- stopped while the compositor does not show it
The compositor takes no frames for 2 seconds, so veil lets go of the video. It starts again from its beginning.
CPU is the daemon’s share of one core for a 1080p, 30 fps H.264 video decoded on an NVIDIA card. Behind a fullscreen window it measured at most one tick of CPU time in 5 seconds.
What a wallpaper costs.
Measured on the build of 2026-10-19 in a headless sway, one 1920x1080 output in fill, 60 seconds after a 5-second warm-up, on an Intel Core i5-11400F with an NVIDIA GeForce RTX 3060 Ti. Nothing has been measured in a real session yet.
| Wallpaper | CPU, share of one core | Frames shown per second | Memory (RSS) | Threads |
|---|---|---|---|---|
| A 1920x1080 PNGno decoder | 0.02 % | — | 20 MiB | 1 |
| H.264, 1080p, 30 fpsnvh264dec (GPU) | 19.5 % | 30.0 of 30 | 203 MiB | 15 |
| H.264, 1080p, 60 fpsnvh264dec (GPU) | 39.7 % | 59.8 of 60 | 203 MiB | 15 |
| VP9, 1080p, 60 fpsnvvp9dec (GPU) | 37.9 % | 59.9 of 60 | 251 MiB | 15 |
| H.264, 2160p, 60 fpsnvh264dec (GPU) | 136 % | 60.2 of 60 | 297 MiB | 15 |
| H.264, 1080p, 30 fpsavdec_h264 (software) | 24.5 % | 30.0 of 30 | 161 MiB | 22 |
| H.264, 2160p, 60 fpsavdec_h264 (software) | 174 % | 60.1 of 60 | 458 MiB | 22 |
- A still picture costs next to nothing after it is drawn: one thread, and one tick of CPU time in a minute.
- A video costs CPU in proportion to its pixels per second, whatever the codec. On NVIDIA the decoder on the GPU costs about 1 %; the rest is converting every frame from NV12 to RGB on the CPU.
- veil’s design goal of under 1 % for a video is not met on this hardware. On Intel and AMD, VA-API could avoid that conversion; that has not been measured.
- Memory, threads and file descriptors stay flat over ten minutes of looping and 500 wallpaper switches, with one allocator arena (MALLOC_ARENA_MAX=1, which the Nix package and the systemd unit set).
The command line, a file, or a socket.
Every veil command is a client of the daemon’s socket, and so can be anything else: a status bar, a key binding, a script in any language.
Command line
Every command takes --help and has a man page. veil set returns once the wallpaper is on screen, and exits with status 1, leaving the old one, if the file cannot be shown.
# every monitor
veil set --input ~/Videos/waves.mp4
# one monitor; the others keep theirs
veil set --input ~/Pictures/photo.jpg --output DP-3
# pause videos until `veil resume`
veil pause
# what each monitor shows, as JSON for scripts
veil status --jsonConfiguration file
~/.config/veil/config.toml holds the daemon’s options and the wallpaper of each monitor. veil config check reads it as the daemon would, and reports a wrong key with its line and column.
[defaults]
mode = "fill" # --mode
bg_color = "#000000" # --bg-color
pause_on = "covered" # --pause-on
[output."DP-3"]
input = "~/Videos/waves.mp4"
mode = "fit"
[output."*"] # every monitor without a section of its own
input = "~/Pictures/wallpaper.png"Control socket
One JSON request and one JSON response per connection, only for processes of your own user. Fields, commands and error codes are only ever added, never renamed or removed.
sock="$XDG_RUNTIME_DIR/veil-${WAYLAND_DISPLAY##*/}.sock"
echo '{"cmd":"ping"}' | socat - UNIX-CONNECT:"$sock"
# {"version":1,"status":"ok"}
echo '{"cmd":"toggle"}' | socat - UNIX-CONNECT:"$sock"
# {"version":1,"status":"ok","result":{"paused":true}}What was run, and by whom.
From veil’s own support matrix. Untested means nobody has tried it yet, not that it is known to fail.
Compositors
sway 1.12 (headless)
Every check of the headless tests and the measurements, 2026-10-19.
VerifiedHyprland 0.56.2
Images, H.264 video on two monitors and the automatic pause, 2026-10-20. Smoothness, lock, DPMS, logout and crash recovery are untested.
Reported workingriver, niri, Wayfire, labwc and other wlr-layer-shell compositors
Should work. No automatic pause: every video counts as visible.
UntestedKDE Plasma (KWin)
Has wlr-layer-shell; nobody has tried.
UntestedGNOME (Mutter)
No wlr-layer-shell.
Unsupported
Graphics
NVIDIA (NVDEC)
RTX 3060 Ti, driver 595.91.07, sway and Hyprland. Every video frame is converted on the CPU.
Reported workingIntel and AMD (VA-API)
The zero-copy path is not verified on any hardware.
UntestedSoftware decoding
In the tests, and by taking the hardware decoders out.
Reported working
Architectures
x86_64
The tarball in a container of six distributions, 2026-10-19.
Reported workingaarch64
The release workflow builds it and runs the tests there; it was never run with a compositor.
Untested
Distributions
Debian 12 and 13, Ubuntu 22.04 and 24.04, Fedora 44, Arch Linux
The prebuilt tarball in a headless sway, 2026-10-19. Animated GIFs need GStreamer 1.26 or newer.
Reported workingNixOS (the flake)
nix run and nix profile, with sway and Hyprland. 2026-10-20 and 2026-11-04.
Verified
The rough edges, stated up front.
veil is a beta. This is what is known to be wrong today; the details, the measurements and the options considered are in the known issues.
A monitor change while a video plays crashes the daemon
The bug is in GStreamer’s waylandsink: it segfaults when the compositor reports a change of any monitor. Pictures are not affected. Run veil under its systemd unit: it is back with its wallpapers within about three seconds.
Known issue 1
A video costs CPU on NVIDIA
About 20 % of a core for 1080p at 30 fps, because every frame is converted to RGB on the CPU.
Known issue 7
Memory grows with video switches without one allocator arena
The Nix package and the systemd unit set MALLOC_ARENA_MAX=1; set it yourself when you start a binary built from source by hand.
Known issue 3
Prebuilt binaries, Nix, or from source.
veil needs a Wayland compositor with wlr-layer-shell, and GStreamer 1.x with the plugin sets base, good, bad and libav at run time. XDG_RUNTIME_DIR must be set, as it is in any desktop session.
Prebuilt
Each release has a tarball for x86_64 and one for aarch64 Linux: the binary, the systemd user unit, the man pages, the shell completions and an install script. Prebuilt binaries are still experimental.
version=1.0.0-beta.1
tarball=veil-$version-$(uname -m)-unknown-linux-gnu.tar.gz
curl -LO https://github.com/ordreth/veil/releases/download/v$version/$tarball
curl -LO https://github.com/ordreth/veil/releases/download/v$version/SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS
tar -xzf "$tarball" && cd "${tarball%.tar.gz}"
./install.sh --prefix ~/.localEach tarball has a build-provenance attestation: gh attestation verify "$tarball" --repo ordreth/veil.
Nix
The flake’s package is wrapped with its own GStreamer plugins and a GStreamer registry of its own, and includes the systemd user unit, the completions and the man pages.
nix run github:ordreth/veil -- daemon # try it without installing
nix profile add github:ordreth/veil # install itBecause of the license, nixpkgs counts veil as unfree. The flake’s own packages allow it by themselves; with the overlay, allow it in your configuration.
From source
You need Rust 1.89 or newer, a C compiler and pkg-config, and the development files of Wayland, libxkbcommon and GStreamer. The install guide lists the packages for Arch, Debian, Ubuntu and Fedora.
git clone https://github.com/ordreth/veil && cd veil
cargo build --release --locked
install -Dm755 target/release/veil ~/.local/bin/veilStarted by hand, give a binary you built yourself MALLOC_ARENA_MAX=1, as in MALLOC_ARENA_MAX=1 veil daemon.
Free to use, source-available, not open source.
veil is licensed under the PolyForm Strict License 1.0.0, with additional permissions. You may read the code and use veil for any noncommercial purpose.
You may
- Use veil for any noncommercial purpose.
- Build veil from its source for your own use.
- Redistribute the release tarballs, unchanged, byte for byte.
- Quote up to 50 lines of the source, with attribution.
- Use the name and the icon to refer to veil.
You may not
- Distribute the source code, or a build of your own.
- Change it, or make new works from it.
- Use it commercially.
- Use the name or the icon for another product or a modified version.
veil takes no pull requests, patches or issues. Reports of how it works on your machine are welcome in GitHub Discussions, and security problems privately, as SECURITY.md says.
veil’s own documents.
These pages are copies of the documents in veil’s repository, so they read the same here and there.
Start
Reference
Project
Beta · 1.0.0-beta.1
Free to use, source-available, and not finished. What is known to be wrong is on this page; what you find on your machine is welcome in the discussions.