// 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.

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):

  1. Windows Boot Manager — \EFI\Microsoft\Boot\bootmgfw.efi and friends.
  2. BLS entries — \loader\entries\*.conf (systemd-boot style), including sort tries/ostree indexes.
  3. UKIs — every *.efi in \EFI\Linux.
  4. 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.
  5. 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:

  1. UKI .cmdline section — a UKI in \EFI\Linux embeds the distro's complete cmdline (root UUID, btrfs rootflags=subvol=, crypt flags). Visor parses the PE section table and reuses it.
  2. /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, honoring ro.
  3. 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:

  1. \EFI\visor\snapshots.conf, generated on the host by tools/visor-snapshot-sync — carries exact per-snapshot cmdlines/kernels, supports multiple OSes. Visor also reads \EFI\visor\snapshot.conf (singular) as a fallback.
  2. Direct btrfs detection (needs the btrfs EfiFs driver): snapper's .snapshots/N/snapshot, Timeshift's timeshift-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.

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.

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.

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.

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.

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.

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

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.