// guide
Features
What Visor does, and how each piece behaves.
Modular builds
Since 1.5.6 Visor is assembled from a registry of named features: every
optional capability is a build-time switch, so you compile exactly the manager
you want — from a tiny text-only booter to everything below. The feature list
lives in features/features.json; each feature has a
blurb, a stub (a no-op compile unit used when the
feature is off, so the build always links), deps (features it
requires), and a config list of boot.conf keys it brings in.
- Profiles — ready-made feature sets:
minimal,standard,hardened,ricer,full(the default profile), andcustom— a profile with no fixed set, meant for an explicitFEATURES=name,name,…list.make list-featuresshows every feature with its on/off state;make list-featuresalso lists the available profiles. - Flags —
make PROFILE=… FEATURES=-blur,+captureadds/removes specific features on top of a profile. Removing something another feature depends on pulls those features out too;make list-featuresshows every feature with its on/off state, andmake check-featuresre-validates the registry and dependency graph. - Installer passthrough —
install.shandvisor installaccept--profileand--features, use the same registry, and filter the generatedboot.confso keys belonging to missing features never reach the ESP. - Manifest — the built binary carries an embedded
VISORFT1build manifest (version, arch, profile, feature registry and image list, font pixel size), andinstall.shwrites a matching feature manifest file on the ESP so — for example — Visor Studio knows exactly what an installed image contains.verify_manifestalso warns if the installed binary doesn't match the build request.
The menu
The graphical menu renders into a full-screen back buffer and presents via
GOP — no flicker, ever. Selection changes slide and cross-fade, pages flip
with an animation, and the whole screen fades in and out around boot. The
selection highlight can be a flat rounded card or a frosted-glass blur.
Entries beyond entries_per_page paginate with chevron indicators.
A text-mode menu (text_menu=1) covers headless setups and firmware
that refuses graphics — Visor falls back to it automatically.
With autoboot=1 the menu is skipped entirely; press the configured
autoboot_key (any by default, or a specific printable
character, navigation key or F1–F12) during the
countdown to interrupt it and open the menu.
Layout adapts to the screen instead of assuming one resolution. Custom and
per-entry icon_size values are clamped to two fifths of the
shorter screen edge (32–512 px), and long entry names truncate with an
ellipsis. Intro labels, the center-info block, the snapshot header and the
version string all clip to the width actually available; the text fitter packs
as many characters as fit even when the space is narrower than a single
ellipsis glyph.
Auto-detection (no boot.conf)
With no config, Visor builds the menu itself. Scan order, across every
readable volume (by default — scan=quick restricts the walk to the
volume Visor booted from plus the ESPs and any XBOOTLDR /boot
partitions, widening back to a full scan if it finds nothing):
- Windows Boot Manager —
\EFI\Microsoft\Boot\bootmgfw.efiand friends. - BLS entries —
\loader\entries\*.conf(systemd-boot style), including sort tries/ostree indexes. - UKIs — every
*.efiin\EFI\Linux. - Raw kernels —
vmlinuz*/bzImage*in\boot,\@\boot, and the volume root, each paired with its initramfs. This scan always runs (a UKI does not hide your plain kernels); it is only skipped when BLS entries already reference the same files. - Distro loaders —
\EFI\<distro>\{shimx64,grubx64}.efi, as a last resort.
Automatic kernel cmdline
A detected raw kernel still needs root= to reach userspace, so Visor
derives an accurate cmdline at boot, with no sidecar file, from the first
available source:
- UKI
.cmdlinesection — a UKI in\EFI\Linuxembeds the distro's complete cmdline (root UUID, btrfsrootflags=subvol=, crypt flags). Visor parses the PE section table and reuses it. /etc/fstab— on any volume the firmware can read (e.g. via an EfiFs driver), the/line supplies the exact root spec and btrfs subvolume flags, honoringro.- GPT root partition type — the Discoverable Partitions root GUID for the build architecture yields
root=PARTUUID=… rw, straight from the partition table via BlockIo — no filesystem access at all.
The boot log records which source was used and the exact line derived.
Discovered entries are checked against the image Visor is running from: if a
kernel or EFI loader resolves to the same file on the same volume, it is
dropped, so a stale firmware entry or a leftover
visor_x64.efi under \EFI\Linux does not become a menu
icon. A same-named path on a second ESP is left alone. BLS/ostree groups keep
their other deployments, with defaults renumbered against the pre-prune
indices and the default command line carried over. Set
exclude_self=0 to allow Visor to list itself.
Live USB hotplug
When the menu is armed, Visor first scans all volumes that are already connected (including USB sticks that were plugged in before the menu appeared). It then watches for volumes that appear later and adds their entries live — the new icon fades and scales in while the existing row slides over. Entries are pinned to the stick's partition GUID and volume handle, so files load from the stick that brought them. Unplugging reverses it: the entries leave and the surviving icons slide back together. Replug never duplicates. Hotplugged and pre-connected volumes derive cmdlines only from sources on the stick itself. Disable with hotplug=0. The setting scan_existing=1 forces scanning of pre-connected volumes even when boot.conf has manual entries.
Btrfs snapshots
Press V to open the snapshot panel for the selected Linux entry and boot any snapshot once. Sources, in order:
\EFI\visor\snapshots.conf, generated on the host bytools/visor-snapshot-sync— carries exact per-snapshot cmdlines/kernels, supports multiple OSes. Visor also reads\EFI\visor\snapshot.conf(singular) as a fallback.- Direct btrfs detection (needs the btrfs EfiFs driver): snapper's
.snapshots/N/snapshot, Timeshift'stimeshift-btrfs/snapshots/<ts>/@, and named subvolumes under.snapshots,snapshots,@snapshots.
The scanner rewrites the entry's rootflags=subvol= to the snapshot
subvolume and shows the newest twelve. Visor never makes a snapshot writable
or performs a rollback — your initramfs must support the distro's read-only
snapshot workflow. snapshots = 0 | manifest | 1 selects the mode.
Boot intro
An optional clip or still played once before the menu, so the boot sequence can have a proper splash instead of jumping straight to the chooser. intro_media takes PNG, BMP, GIF, MJPEG MP4/MOV/M4V or VBG — the same decoders the animated background uses. Any key skips it and goes to the menu.
- No extra features needed. A still image requires nothing beyond
gui. An animated intro needs only the decoder for its own format (anim_giforanim_mp4), because the intro deliberately does not hard-require them: which decoder a given file needs is a runtime question, not a build-time one. A build with no decoders still acceptsintro_mediaand simply draws the still case. - Never a failed boot. An unset, unreadable or undecodable path, a format the build cannot decode, or a truncated clip all fall through to the menu. The feature is inert until
intro_mediais set, so the keys are safe to add to an existing config. - Automatic or manual. With
intro_auto_continue=1(default) the intro hands over by itself afterintro_durationand goes to the chooser or boots the selected default, perintro_action. With it off, the intro waits for a key — holding the last frame if it isn't looping — andintro_durationstops being a deadline.
Contributed by @4ceWolf as an animated-intro handoff, and reworked here into an optional feature that follows the rest of the build registry. See Configuration → Boot intro.
Animated backgrounds
background= accepts PNG and BMP for static wallpapers, plus GIF and MP4/MOV (MJPEG) for animated looping wallpapers. The decoders are self-contained — GIF89a (LZW, disposal, loops) and MJPEG MP4 (bounded decode, loop budget) — with bounded memory; the accent palette is taken from frame one and the frosted-glass panels reuse their blur while the animation plays so the menu stays fast. The MP4 decoder validates every sample table and bounds each sample offset and length against the file size before use, so a malformed clip cannot read out of bounds. Frames advance on a wall-clock schedule that drops time on a slow redraw instead of spiralling, and the animation stops cleanly when its loop budget is spent. For best results use the compact VBG format via visor convert --vbg — it is ~10–20× smaller than MJPEG and decodes cheaper at boot.
Tip — try background=\EFI\visor\backgrounds\witch.vbg from the gallery; it is a tiled-quantized delta stream with motion compensation that stays smooth even on slow firmware GOPs.
Live clock
A firmware-RTC clock rendered as a live overlay outside the selection cache — it ticks without forcing a full redraw. Configure with clock / show_clock (unified colorSpec: 0, 1, a palette role or #RRGGBB), clock_size, clock_format (24h/12h), clock_seconds, clock_position (topright, bottomleft, center … hyphens/spaces equivalent, auto-avoids the centered header), plus clock_date/clock_date_format (long, iso, dmy, mdy) and decoration clock_blur (frosted backing) / clock_shadow. See Configuration → Clock.
Clock time sync
UEFI has no time sync of its own. On a machine where nothing writes the RTC back, the hardware clock free-runs and slowly diverges from real time while the OS hides the divergence behind NTP — so the menu clock shows a time that is quietly wrong.
With clock_sync=1 (off by default) every clock read passes through a correction the OS publishes in \EFI\visor\time.sync as a single signed integer, offset=<seconds>. The file holds a difference, not a timestamp, because GetTime returns local time on some firmware and UTC on others and nothing running pre-OS can tell which. Corrections larger than a day are ignored: that is an RTC that has lost its battery, not drift.
visor time-sync is the host side. With no options it reports the gap between the hardware and system clocks and which direction each is wrong in; --publish writes the correction, --write-rtc writes the system clock back to the hardware clock so it stops drifting, and --install / --remove manage a self-contained systemd timer that runs every --interval hours (default 6) and sets clock_sync=1 for you. --utc covers hardware clocks already set to UTC.
Screensaver
Two stages while the graphical menu is idle: after screensaver_delay (default 60 s) the UI cross-fades to the bare background (plus the clock if screensaver_clock=1); after screensaver_blank (default 600 s) the panel blanks to black and the render loop idles. The next key or pointer move cross-fades back and restarts the boot countdown so a stray key never instantly boots. Idle is suspended during the countdown, the command-line editor and the file browser. Animated wallpapers keep playing while dimmed, and are fully idle while blanked.
Enable with screensaver=1 or the shorthand screensaver=60 (on with 60 s delay). Configuration → Screensaver lists all keys.
Boot sound (built-in HDA audio)
Visor bundles a self-contained Intel HD Audio driver that plays a short sound at startup before the OS takes over — no external driver and no dependence on the OS having configured audio. It is built for the common case of a small menu_sound sting or a bundled occasional flash moment; see Architecture → Audio for how it fits the boot flow and Configuration → Sound for the key.
- Headphone priority. When a codec reports jack-presence, a plugged-in headphone pin is chosen over the built-in speakers; speakers are the fallback when nothing is plugged in. Other output pins sharing the same converter are muted for the duration so a sound never plays out of both.
- Plays once. The buffer is padded with silence and played as a prepared stream that drains to the audible end, so a short sting is not cyclically replayed by the DMA engine.
- Simultaneous fade-in. The sound starts at the same instant the menu begins to fade in, rather than playing to completion first, so picture and audio arrive together.
- Volume.
menu_sound_volume(0–100, default 100) scales the recorded PCM in software, so it is independent of the firmware mixer. Values above 100 are clamped — the sound is never amplified.
TPM2 measured boot
When a EFI_TCG2_PROTOCOL is present Visor measures the on-disk boot.conf into PCR 5 and the booted kernel command line (including its terminating NUL) into PCR 12 via HashLogExtendEvent, matching systemd's tpm_log_load_options layout so a sealed policy stays portable. The active PCR banks are published as volatile LoaderTpm2ActivePcrBanks and advertised in LoaderFeatures. Configure with tpm / measure and tpm_pcr_config / tpm_pcr_cmdline (0–23). Set tpm=0 to skip entirely.
A firmware TPM that is enabled but unusable can no longer hang the boot.
Visor writes the VisorTpmStage NVRAM marker before probing and
clears it afterwards; if a boot finds the marker still set to probing, the
previous boot died in LocateProtocol/GetCapability()
and measured boot stays off on this and later boots. Clear the variable to
re-enable it, or use tpm=0 permanently. The capability block is
pre-filled with 0xFF and validated (full Size, sane
ManufacturerID), so firmware that returns success without writing
anything is not trusted.
Boot Loader Interface
Visor speaks the systemd Boot Loader Interface (vendor GUID 4a67b082-0a4c-41cf-b6c7-440b29bb8c4f). When loader_vars=1 (default, alias loader_interface) it publishes LoaderInfo, LoaderFirmwareInfo/Type, LoaderDevicePartUUID, LoaderImageIdentifier, LoaderFeatures, LoaderEntries, LoaderTimeInitUSec/MenuUSec/ExecUSec, LoaderTpm2ActivePcrBanks, and honors LoaderConfigTimeout / LoaderConfigTimeoutOneShot and LoaderEntryDefault / LoaderEntryOneShot (one-shot vars are consumed whether or not they match). One-shot is the one-boot mechanism for OS agents like bootctl and systemd-bless-boot. Disable all of it with loader_vars=0.
Accent colors (Material You)
With accent=1, Visor extracts a seed color from the wallpaper —
Celebi quantization then scoring, the same pipeline as Android's Material You —
and builds a tonal palette used for icons, underline, and text. Variants:
tonal, vibrant, expressive, fidelity, content, neutral, monochrome
(accent_variant=). The whole HCT/CAM16 color pipeline runs in Q16
fixed-point integer math (firmware has no floating point) and tracks the
reference Material You implementation closely — chromatic wallpapers match
hue within half a degree.
Immutable distros & OSTree
BLS configs from OSTree-based systems (Fedora Silverblue/Kinoite, etc.) are grouped into one entry per OS with a version panel: V cycles deployments (current, rollback, pinned), so an atomic system shows one clean icon instead of three near-identical entries.
Non-FAT filesystems
UEFI firmware only reads FAT. To auto-detect kernels living on ext4, btrfs,
xfs, and friends, Visor loads EfiFs drivers
from \EFI\visor\drivers\. The installer (and visor drivers)
detects the /boot filesystem and downloads the matching driver.
Drivers are deferred: loaded at startup but only started when a file
actually needs them or the fast scan finds nothing, keeping the menu at
~1 second even with the btrfs driver installed.
Under Secure Boot, shim >= 16 replaces the firmware's
StartImage with one that frees the image when its entry point
returns. A filesystem driver started that way would work once and then run from
freed memory. When a loaded image carries shim's
SHIM_LOADED_IMAGE protocol, Visor calls the PE entry point
directly instead, so the driver stays resident for the rest of the boot.
Visor's driver build (read cache)
Visor installs its own build of the drivers from
IO-ZetZor/EfiFs rather than the
stock upstream release. The only intended difference from upstream is a
read cache in grub_disk_read(): a lazily-allocated,
direct-mapped 64-entry cache of 4 KiB blocks, capped at 256 KiB. On a
btrfs image boot this cut firmware block reads from 1165 to 4.
- Reads under 128 KiB go through the cache; larger bulk reads pass straight through, so sequential streaming is never slowed down.
- Reads that run past the end of the medium, or on media of unknown size, bypass the cache entirely. An allocation failure degrades to a plain uncached read — the cache can never make a boot fail.
- The cache is flushed when a device is mounted or unmounted, and its key includes the medium's identity and geometry, so a remount or a size change cannot serve stale sectors.
Every driver downloaded is pinned to a SHA-256, and a mismatch
refuses the install rather than writing the file. Set EFIFS_URL to
install the stock upstream drivers instead; the installer says out loud that an
overridden source is not checksum-pinned.
Both architectures are built with edk2-stable202608, the same
toolchain upstream uses, and 28/28 filesystems pass under QEMU on x64 and
aarch64. The aarch64 drivers are real EDK2 PE images with a relocation
directory — they are not converted from the gnu-efi ELF, which cannot
work, since that path's entry point is an ELF self-relocator rather than a
firmware entry point.
Tip — if you are patching the cache, note that the EDK2 build enumerates sources per driver in EfiFsPkg/*.inf. A new file in src/ must be added to all 37 manifests or every driver fails to link — the gnu-efi/Makefile build picks it up automatically, the EDK2 build does not.
Recording a whole boot
The recboot feature records the entire boot — fade-in, menu, boot
sound, fade-out — to a single MJPEG+PCM .avi in
\EFI\visor\shots\, useful for showing someone a boot problem or
making a wallpaper out of your own boot.
- One marker, one recording. Drop an empty
\EFI\visor\rec.armfile on the ESP and the next boot is recorded end to end. Visor deletes the marker as it starts, so it records exactly one boot and there is nothing to switch off afterwards. - Overrides in the marker. The file may carry
fps,widthandquality. - Sound. Audio is muxed in when the
audiofeature is built in; without it the video is silent. - Fixed frame rate. Frames are captured on a wall-clock schedule rather than whenever a draw happens, so the recording runs at a steady rate instead of speeding up or stalling with how busy the menu is.
Themed pointer
pointer=1 adds mouse and touchpad navigation with click-to-select;
keyboard navigation is core and always works. cursor (alias
cursor_image) replaces the built-in white arrow with a PNG or BMP, and
is settable from a theme file — so a theme can ship its own pointer.
default, arrow, none or off
keeps the built-in one.
cursor_sizeis the rendered edge length in pixels, clamped to 8–128;0uses the image's own width.- The image is scaled to a single square size, so a non-square source gets squashed — a square image with a transparent background is what you want.
cursor_hotspotpicks which pixel of the image is the pointer, as"X,Y"in image pixels orcenterfor a crosshair.topleft/tipmean 0,0.
See Configuration → Icons for the keys.
File browser
Press B at the menu — or run browse [PATH] in the
recovery shell — to open a full-screen browser over every readable volume.
The boot volume comes first; others are labelled by partition UUID. Entries
show sizes and sort directories-first, case-insensitively, with a merge
sort, so even very large directories don't stall the menu.
- Enter — open a directory, or boot the selected file.
- Backspace/← — parent directory.
- Tab/→ — next volume; PgUp/PgDn and the mouse wheel — page.
- Esc (or B again in the GUI) — close.
Booting from the browser is a one-shot override: a kernel boots with a
sibling initrd* paired automatically, an initrd sets the initrd
override, and a .efi is chainloaded. The load is pinned to the
volume the file was picked from (partition GUID and volume handle),
so an MBR stick without a GUID can't be answered by a same-named file on
the ESP. Rough edges: booting needs a selected entry to attach the override
to, very slow USB media can make large listings take a moment, and
filesystems without a loaded driver (ext4 without the EfiFs driver) don't
appear in the browser.
Windows
Windows entries chainload bootmgfw.efi by device path, so BitLocker
and the BCD store behave exactly as if the firmware had booted Windows
directly. If an entry has no kernel path at all, Visor searches for the
Windows Boot Manager across volumes (honoring the uuid= pin).
If chainloading through Visor keeps invalidating BitLocker's PCR 7 binding,
set windows_bootnext=1. Visor then sets BootNext to
the firmware's own Windows entry and resets, so Windows starts without shim or
Visor in its measured boot. The handoff only uses an entry on the selected
partition, skips itself when the variable cannot be stored, and falls back to a
normal chainload if it did not start Windows. sha256-pinned
Windows entries stay on the verified chainload path.
Editing & recovery
- e edits the kernel cmdline for one boot (
editor=1). The editor is a full-screen centered card with word wrapping, vertical scrolling, a scrollbar and a visible caret. Long command lines wrap and scroll instead of truncating; Home, End and Delete work. Password prompts mask input with asterisks and toggle reveal, and reset the scroll position when they close. - Esc at the boot menu opens the options / rescue console — same as after a failed boot: list and pick entries, edit kernel options, read
boot.log, reboot, or enter firmware setup. Esc there returns to the menu. recovery_entries=1auto-adds(recovery)variants bootingsystemd.unit=rescue.target nomodeset.- If a boot fails, Visor drops into a recovery console (commands like
log,browse,reboot) instead of hanging — see Debugging.
When a LUKS entry (luks=1) is selected, Visor shows a passphrase prompt. F2 toggles showing what you typed (check your keyboard layout). luks_confirm=1 requires typing the passphrase twice — a mismatch reprompts instead of booting. luks_verbose=1 removes quiet splash from the cmdline so the initramfs retry prompt is visible when a passphrase is rejected.
Immunity to Windows
Two layered defences that take over where OS updates like to break things: Visor repairs the boot entry and fallback path that Windows routinely rewrites, and it catches and fixes corrupted GPT tables before they strand a disk.
- Boot self-heal — at every startup Visor checks the damage an
OS update can leave behind: a Visor Boot
Boot####entry that was removed or pushed downBootOrder, and a\EFI\BOOT\BOOTx64.EFIfallback that Windows feature updates and 24H2 replace with a copy ofbootmgfw.efi. Nothing is written when there is nothing to fix, and removable media are never touched. A malformed or oversizedBootOrderis left exactly as it is rather than being replaced with a single Visor entry, and a zero-size fallback binary is never restored. Tricks:boot_order=first(keep Visor at the head ofBootOrderafter a normal boot-manager boot),boot_order=ensure(only recreate a missing entry),boot_order=off,restore_fallback=1, andselfheal=0to skip the pass. - GPT corruption repair — at menu startup Visor scans whole-disk block devices for GPT damage. A disk with a valid backup header but a damaged primary (corruption, bad CRC, clobbered signature) gets a warning modal: overview, details, confirm (type YES), working, done. The primary GPT is rebuilt from the verified backup only when the plan is confirmed safe — never when the layout is invalid, the backup is itself corrupt, or the disk is read-only.
- The same engine is available from the recovery console:
gptscans for damage,gpt Ndetails one disk, andgpt repair Nrebuilds the primary header from the backup.