buffr — configuration

User config is a single TOML file. Every key has a default; the loader emits an error with a line/column span when a key is misspelt, unknown, or has the wrong type. A copy-pasteable defaults-equivalent lives at config.example.toml at the repo root.

File location

PlatformPath
Linux$XDG_CONFIG_HOME/buffr/config.toml
macOS~/Library/Application Support/buffr/config.toml
Windows%APPDATA%\buffr\config.toml

Path resolution goes through directories::ProjectDirs::from("sh", "kryptic", "buffr"). Override per-run with --config <PATH>.

CLI flags

FlagEffect
--print-configPrint the resolved (defaults + user overrides) config; exit 0.
--check-configValidate the config file; exit non-zero on parse / schema error.
--config <PATH>Override XDG-discovered config path.
--homepage <URL>Override general.homepage for this run only.

Both --print-config and --check-config short-circuit before CEF initializes, so they're safe to run on a headless host.

Schema

[general]

KeyTypeDefaultNotes
homepagestringhttps://example.comInitial URL on first window.
leaderstring\Exactly one character. Validated.

[startup]

KeyTypeDefaultNotes
restore_sessionboolfalsePhase 5 work; parsed but no-op for now.
new_tab_urlstringabout:blankURL for tab_new.
KeyTypeDefaultNotes
default_enginestringduckduckgoMust reference a [search.engines.<name>] block.

[search.engines.<name>] blocks define each engine:

[search.engines.duckduckgo]
url = "https://duckduckgo.com/?q={query}"
prefix = "ddg"  # optional

[search.engines.github]
url = "https://github.com/search?q={query}"
prefix = "gh"

{query} is replaced with the URL-encoded omnibar input.

prefix is an optional shortcut keyword. When set, an omnibar input of <prefix> <query> routes to that engine instead of default_engine — e.g. gh tokio searches GitHub, g rust closures searches Google, plain cats falls through to the default. Bare prefix words with no query (e.g. g) fall through to the default so they still produce a useful result. Prefix collisions across engines are rejected at config validation time.

[theme]

KeyTypeDefaultNotes
accentstring#7aa2f7Hex color used for the status line.
modeenumautoauto | dark | light.

[privacy]

KeyTypeDefaultNotes
enable_telemetryboolfalseReserved. buffr never sends telemetry.
clear_on_exitstring[][]Phase 5+; e.g. ["cookies", "history"].

[keymap.<mode>]

Mode is one of normal, visual, command, hint. Each entry maps a vim-notation key sequence to a PageAction:

[keymap.normal]
"j" = "scroll_down"
"5j" = "scroll_down(5)"
"/" = "find(forward = true)"
"<Esc>" = "enter_mode(\"normal\")"

The full default keymap lives in keymap.md.

Action notation

  • Unit variants — bare snake_case name. "scroll_down", "reload", "tab_close", etc.
  • Count-bearing scrollsname(N) where N >= 0. Applies to scroll_up, scroll_down, scroll_left, scroll_right.
  • Findfind(forward = true) or find(forward = false).
  • Mode transitionenter_mode("<mode>") with a quoted mode name.

Anything else surfaces a validation error pointing at the offending key.

[idle_inhibit]

Keeps the screen awake while video (or optionally audio) is playing in the focused window. Backed by four platform implementations:

  • Linux Waylandzwp_idle_inhibit_manager_v1 protocol.
  • Linux X11org.freedesktop.ScreenSaver.Inhibit over D-Bus.
  • macOSIOPMAssertionCreateWithName(NoDisplaySleepAssertion).
  • WindowsSetThreadExecutionState(ES_DISPLAY_REQUIRED) on a worker thread.

The inhibitor is acquired and released at runtime; no restart needed.

Note (v0.3.0): The JS→Rust read-back path for __buffr_video_active is scaffolded but not yet wired end-to-end. The section parses and the inhibitor plumbing compiles on all platforms, but the inhibitor will not activate until the signal path lands in a future release.

KeyTypeDefaultNotes
enabledbooltrueMaster switch. false disables the feature entirely — no inhibitor is ever acquired.
inhibit_audio_onlyboolfalseWhen true, audio-only activity (no video) also triggers the inhibitor.
require_focusbooltrueWhen true, the inhibitor is held only while the buffr window has OS-level focus. Set to false to inhibit even when the window is in the background (useful for PiP setups).
[idle_inhibit]
enabled = true
inhibit_audio_only = false
require_focus = true

Hot reload

The watcher uses notify with a 250ms debounce. On a successful reload, the keymap only is swapped on the running engine — homepage, theme, startup, and search settings still require a restart for now (full hot-apply is Phase 5+ work). A failed reload (parse or validate error) is logged and the previous config stays live.

Validation rules

  • general.leader must be exactly one character.
  • search.default_engine must reference an existing [search.engines.<name>] block.
  • search.engines.<name>.prefix (when set) must be non-empty and unique across all engines.
  • Every keymap binding's key sequence must parse via the engine's parse_keys, and its action notation must match the table above.
  • Unknown top-level keys, unknown nested keys, and unknown enum variants all error out (#[serde(deny_unknown_fields)]).