Repository navigation
Releases
veiland 0.3.1
A packaging-only patch release for Nix users. Nothing in veiland itself
changed since v0.3.0 -- no core, protocol, SDK or plugin code was touched.
Fixed
- NixOS: the Python plugins could not run. Every installed
veiland-*
Python widget died at startup with "cannot load libgbm". They now start
normally. (8984950)
Full changelog: v0.3.0...v0.3.1
veiland 0.3.0
veiland 0.3.0
Veiland gains a GLSL shader tier: veiland-shader runs a
Shadertoy-convention fragment shader as a lock-screen layer. Seven
shaders ship in the binary, and a path to your own .frag runs that
instead, so a shader you like on Shadertoy usually drops in unchanged.
Upgrading from 0.2.0 is a straight upgrade: nothing was removed and no
configuration changed.
Added
The shader plugin
veiland-shadercompiles a Shadertoy-conventionmainImageshader
and renders it into its region every frame. You write only that
function; the version directive, precision qualifiers, uniform
declarations, and themain()that calls it are prepended for you.
fragCoordarrives with Shadertoy's bottom-left origin.- Seven embedded presets, needing nothing installed:
nebula,
deepfield,starfield,moon,meteor,warp, andplasma.
Preset names are a stable interface, like a CLI flag. pathruns a.fragfile of your own, and wins overpresetwhen
both are set.- The full single-pass Shadertoy uniform set is declared, so a shader
mentioning one compiles even where the value is not meaningful here.
iTime,iTimeDelta,iFrame,iFrameRateandiResolutionare
live;iChannel0is a 256x256 tiling noise texture;iMouseis zero,
since plugins receive no pointer events;iDatefills only its
seconds-since-local-midnight component. opacitybelow 1.0 switches the layer to premultiplied transparency,
so a shader written with alpha in mind composites over what sits under
it. At the default 1.0 the layer is opaque and the shader's alpha is
ignored, which is the right default because Shadertoy shaders write
undefined alpha.render_scaleandmax_fpsare the thermal knobs.max_fpsdefaults
to 30 rather than the compositor rate, because a full-screen fragment
shader runs your maths on every pixel of every frame.loop_secondswrapsiTimeat a period you choose, for shaders with
a natural cycle or for very long locks, where a floatiTimeloses
precision as it climbs.veiland-shader --check [FILE]compiles a shader and prints the
driver's own compile log, so a syntax error surfaces without locking
the screen. It exits 0 when the shader compiles; with no argument it
checks whatever your config selects.- A bad shader never costs you the scene. An unknown preset, an
unreadable or oversized file, or a compile error logs the reason with
the compiler's full output and draws a dark fill, with the plugin
still running.
Scenes, widgets, and the SDK
- A Deep Field astronomy example scene
(docs/examples/deepfield-astronomy.toml): four shader layers, three
of them transparent, under a markup clock and an ephemeris HUD.
Entirely procedural, with no image asset. - The
markupwidget gained a{jd}substitution, the Julian Day
Number that astronomers count in. GbmEgl::new_es3inveiland-plugin, for plugins whose shaders need
a GLES 3 context to compile#version 300 es.
veiland 0.2.0
veiland 0.2.0
Veiland gains a Python plugin tier: a full SDK plus nine ready-made
widgets, so a scene can now show the weather, what's playing, or your
network state without writing Rust. Wallpapers can blur, plugins can be
anchored to a corner instead of pinned to pixel coordinates, and the
text tier moved to Pango markup.
Upgrading from 0.1.0: read Removed and Changed first. The
clock and label plugins are gone, and PAM no longer runs the account
phase.
Added
Python plugin tier
veiland_plugin.py, a Python SDK mirroring the Rust one: wire codec,
Connection, GBM buffer allocation,FramePacer, andBufferChain.
Plugins are ordinary Python programs; the wire format was never tied
to Rust, and this is the proof.- Four companions:
veiland_text(Pango text),veiland_svg(SVG icon
rendering),veiland_dbus(D-Bus, no async runtime), and
veiland_layout(content anchoring and debug borders). - Nine widgets installed as
veiland-*commands on every packaging
target:markup,weather,now-playing,avatar,wifi,
ethernet,bluetooth,battery,shape. markupis the new text tier: one block of Pango markup with ten
substitutions:{user},{name},{host},{time},{date},
{uptime},{loadavg},{kernel},{distro},{distro_version}.
{time}and{date}take a strftime format; a bad one shows the text
you typed instead of crashing.weatherreads Open-Meteo by city name or latitude/longitude. No API
key, and the fetch uses only the standard library, so it adds no
dependency beyond the shared widget stack. Eleven CC0 icons included.now-playingshows the current MPRIS track, read-only.shapedraws a rounded colored rectangle, a backdrop to group other
widgets against.- Python tooling in CI: ruff, mypy
--strict, and pytest.
Core
- Anchored region placement:
halign/valignwith fraction-of-surface
width,height, andmargin(margin_x/margin_yper axis). A
widget anchored to a corner lands in the same relative spot on 1080p
and 4K, instead of at fixed pixel coordinates. --helpand--version. Version carries the git revision on builds
offmaster, so a bug report says which build it came from.VEILAND_GL_DEBUG=1for GL diagnostics: the driver debug callback
where available, with aglGetErrorfallback.
Plugins
wallpapergainedblur,darken, andblur_regions: blur the
whole surface, then tint rounded regions on top as frosted cards.particlesgained an opt-intwinkle(withtwinkle_speedand
twinkle_depth) for a shimmering starfield.- Optional labels beside the glyph on
wifi,bluetooth, and
battery;avatargained a rounded-squareshape.
Scenes and docs
- Four composed example scenes with screenshots:
aurora-corners,
aurora-card,aurora-dashboard, anddeepfield. - A documentation website (Zola), with per-plugin reference pages
generated from a single source and checked in CI. docs/plugin-python.md, the Python SDK reference.- Manual VM packaging tests (
scripts/vmtest/) that boot real Debian,
Fedora, and Arch guests, install the built package, and verify the
widget dependency closure resolves.
Changed
- PAM now runs
pam_authenticateonly, notpam_acct_mgmt. A
correct password was intermittently rejected right after a fast logout
and login, because the account phase returned a transient error while
logind tore down the previous session. A locker re-verifies an
existing session; deciding whether an account may start one is a
login manager's job, and applying it here could lock you out of your
own live session with no way to change the password. Shipped PAM files
drop the now-deadaccountline. Existing installs keep their current
file, where the stale line is simply never evaluated. Trade-off: an
account disabled while the screen is locked still unlocks with a
correct password. The session is already open either way. Configurenow carries the plugin's region dimensions rather than the
full surface size, so a plugin assigned a region renders at that
region's size instead of drawing full-surface and being squashed into
it.- Python widgets are referenced by bare
veiland-*name in the example
scenes, resolved onPATHlike the Rust plugins.
Removed
- The
clockandlabelplugins.markupreplaces both, with
Pango markup, live substitutions, and font matching that follows the
system's configuration. Scenes using them need
binary = "veiland-markup"and atextkey; see
docs/examples/for worked configurations.
Fixed
- CPU-rendered plugins flickered because a single buffer was redrawn in
place while the host was still sampling it.BufferChaindouble-buffers
and fixes it. - Dmabufs that a driver only accepts as external textures rendered black
(NVIDIA marks linear and CPU-written buffers this way, so every CPU
plugin was affected). The core now samples them via
GL_TEXTURE_EXTERNAL_OES, and falls back cleanly on stacks without the
extension. - Glyphs bled into their neighbours in the text atlas; a 1px gutter fixes
it. - A glyph the rasterizer can't render is now reported once instead of
silently vanishing. Hairline font weights drop digits on some fonts,
which previously looked like a bug in the clock.
veiland 0.1.0
First release. Veiland is a GPU lock screen for Wayland that you compose
from layers, each one a separate program.
Added
Core locker
ext-session-lock-v1lock surface; works on any compositor implementing
the protocol (tested on Hyprland and Sway). The compositor enforces the
lock, so the session fails closed even if veiland crashes.- PAM authentication against the
veilandservice (auth+account
phases), run on a worker thread so a wrong password never freezes the
animation. - Configurable password indicator, always composited on top of all plugin
output. - Multi-monitor support with per-plugin output targeting, and HiDPI-aware
text rendering viaveiland-text(cosmic-text backend). - Reliability across suspend/resume, DPMS off/on, and monitor
unplug/replug — including unplugging every monitor at once and
hot-plugging a display in while locked. - Password-buffer hygiene:
mlock'd, zeroed after the PAM call, and the
core setsprctl(PR_SET_DUMPABLE, 0)at startup to deny same-UID
ptrace/proc-mem reads and core dumps (VEILAND_ALLOW_DUMP=1opts out).
Plugin system and protocol
- Process-isolated plugins: every layer is a separate program, spawned over
asocketpairand speaking a versioned tagged-message protocol on an
AF_UNIX SOCK_SEQPACKETsocket. Plugins never receive keyboard input and
have no unlock path — the API surface is absent by construction. - Zero-copy GPU rendering: plugins allocate buffers via GBM, render with
their own EGL/OpenGL context, and pass DMA-BUF file descriptors to the
core viaSCM_RIGHTS; the core imports them asEGLImagetextures. No
pixel data crosses CPU memory. - Explicit sync: frames carry a sync-fence fd where the driver supports it,
with aglFinishfallback negotiated automatically. - Scene composition: plugins layer by
z_index, can target specific
monitors, and are configured through[[plugin]]tables in
~/.config/veiland/config.toml. - A crashed or misbehaving plugin costs its own layer, never the locker:
malformed messages close the plugin's socket and a fallback is drawn for
its region. veiland-plugin, the Rust plugin SDK: imperative primitives
(Connection,FramePacer,DmaBuffer,GbmEgl) the plugin author
drives from their ownmain(). The wire format is documented and not
tied to Rust.veiland-protocol, the wire-format implementation, with a cargo-fuzz
harness for the codec.
Bundled plugins (14)
wallpaper— image wallpaper.clock— time display.label— static text.vignette— darkened-edges overlay.particles,sakura,snow,rain,embers,fireflies— the
particle family: drifting particles, falling petals, dendritic ice
crystals, slanted rain streaks, rising sparks, wandering glow.gradient,parallax,blobs— ambient backgrounds: flowing color
ramp, layered bokeh depth, lava-lamp metaballs.raymarcher— raymarched gyroid tunnel drift.
Packaging
- NixOS flake: package, dev shell, and checks, plus a NixOS module
(services.veiland.enable = true) that installs the binary and reference
plugins and registers the PAM service. - Arch Linux
PKGBUILD(AUR). - Debian package for Debian testing (trixie), with the PAM service bundled.
- RPM spec for Fedora/RHEL, with the PAM service bundled;
.deband.rpm
artifacts are built by CI and attached to releases. - Reference PAM service files and an annotated example config under
packaging/.
Documentation
docs/architecture.md— full write-up: trust boundaries, module map,
security model.docs/protocol.md— the wire-format specification (the spec is
authoritative over the implementation).docs/plugin-api.md— plugin SDK reference.docs/plugin-authoring-claude.md— drop-in context file for writing
plugins with an AI coding assistant.docs/config.md— configuration reference.- Ready-made example scenes in
docs/examples/, matching the README
gallery (shinkaicomposes ten plugin instances across two monitors). SECURITY.mdandCONTRIBUTING.md.