Configuration Guide
Location: ~/.config/dumber/config.toml
Formats: TOML (recommended), JSON, YAML
Database
| Key |
Type |
Default |
Description |
database.path |
string |
~/.local/share/dumber/dumber.db |
Database file path |
History
| Key |
Type |
Default |
Valid Values |
Description |
history.max_entries |
int |
10000 |
> 0 |
Maximum number of history entries |
history.retention_period_days |
int |
365 |
> 0 |
Days to keep history (1 year) |
history.cleanup_interval_days |
int |
1 |
> 0 |
How often to run cleanup |
Search Configuration
| Key |
Type |
Default |
Description |
default_search_engine |
string |
"https://duckduckgo.com/?q=%s" |
Default search engine URL template (must contain %s placeholder) |
search_shortcuts |
map |
See defaults |
Map of shortcut aliases to URLs |
Example:
# Default search engine - used when no shortcut is specified
default_search_engine = "https://duckduckgo.com/?q=%s"
# Alternative search engines:
# default_search_engine = "https://www.google.com/search?q=%s"
# default_search_engine = "https://www.startpage.com/search?q=%s"
# default_search_engine = "https://search.brave.com/search?q=%s"
Default shortcuts (usage: !shortcut query):
# Examples:
# !ddg golang → DuckDuckGo search for "golang"
# !g rust tutorial → Google search for "rust tutorial"
# !gi cats → Google Images search for "cats"
# !gh opencode → GitHub search for "opencode"
# !yt music video → YouTube search for "music video"
[search_shortcuts.ddg]
url = "https://duckduckgo.com/?q=%s"
description = "DuckDuckGo search"
[search_shortcuts.g]
url = "https://google.com/search?q=%s"
description = "Google Search"
[search_shortcuts.gi]
url = "https://google.com/search?tbm=isch&q=%s"
description = "Google Images"
[search_shortcuts.gh]
url = "https://github.com/search?q=%s"
description = "GitHub"
[search_shortcuts.yt]
url = "https://youtube.com/results?search_query=%s"
description = "YouTube"
These settings control the dumber dmenu CLI command for rofi/fuzzel integration.
| Key |
Type |
Default |
Description |
dmenu.max_history_days |
int |
30 |
Number of days of history to show (0 = all) |
dmenu.show_visit_count |
bool |
true |
Show visit counts in output |
dmenu.show_last_visited |
bool |
true |
Show last visited dates in output |
dmenu.history_prefix |
string |
"🕒" |
Prefix for history items |
dmenu.shortcut_prefix |
string |
"🔍" |
Prefix for shortcuts |
dmenu.url_prefix |
string |
"🌐" |
Prefix for URLs |
dmenu.date_format |
string |
"2006-01-02 15:04" |
Go time format string |
dmenu.sort_by_visit_count |
bool |
true |
Sort by visit count instead of recency |
CLI Usage:
# Pipe mode (default) - outputs history for rofi/fuzzel with favicons
dumber dmenu | rofi -dmenu -show-icons -p "Browse: " | dumber dmenu --select
dumber dmenu | fuzzel --dmenu -p "Browse: " | dumber dmenu --select
# Interactive TUI mode
dumber dmenu --interactive
# Override history days via CLI flag (show last 7 days)
dumber dmenu --days 7
# Sort by most visited instead of recency
dumber dmenu --most-visited
# Combined: most visited from last 14 days
dumber dmenu --days 14 --most-visited
Omnibox
| Key |
Type |
Default |
Valid Values |
Description |
omnibox.style |
string |
"command" |
multiplexer, command, minimal |
Omnibox visual treatment |
omnibox.initial_behavior |
string |
"recent" |
recent, most_visited, none |
Initial history display behavior; press Ctrl+R in the omnibox to switch |
omnibox.max_history_days |
int |
30 |
>= 0 |
Days of history used by omnibox suggestions, for both typed searches and the empty-input list (0 = all history) |
omnibox.auto_open_on_new_pane |
bool |
false |
- |
Automatically open the omnibox after creating a new pane |
Changing omnibox.style takes effect after restarting Dumber.
Example:
[omnibox]
style = "command" # Soft terminal-inspired command palette
initial_behavior = "recent" # Show recent history when omnibox opens
max_history_days = 30 # History window for omnibox searches and initial list
auto_open_on_new_pane = false
# Alternative visual treatments:
# style = "multiplexer" # Segmented terminal multiplexer treatment
# style = "minimal" # Bare terminal typography and hairlines
# Alternative options:
# initial_behavior = "most_visited" # Show most visited sites
# initial_behavior = "none" # Show no initial suggestions
Note: omnibox.max_history_days is non-destructive: it never deletes history, it only limits which entries omnibox suggestions consider. most_visited ranks by lifetime visit counts among URLs whose last_visited falls inside the window.
This key replaced omnibox.most_visited_days. Run dumber config migrate to rename the legacy key while preserving its value. The derived environment variable is DUMBER_OMNIBOX_MAX_HISTORY_DAYS; the old DUMBER_OMNIBOX_MOST_VISITED_DAYS is no longer read.
Logging
| Key |
Type |
Default |
Valid Values |
Description |
logging.level |
string |
"info" |
trace, debug, info, warn, error, fatal |
Log level |
logging.format |
string |
"text" |
text, json, console |
Log format |
logging.max_age |
int |
7 |
>= 0 |
Days to keep logs |
logging.max_files |
int |
100 |
>= 0 |
Session log files to keep (0 disables count cleanup) |
logging.log_dir |
string |
~/.local/state/dumber/logs |
any path |
Log directory |
logging.enable_file_log |
bool |
true |
- |
Enable file logging |
logging.capture_console |
bool |
false |
- |
Capture browser console to logs |
logging.capture_gtk_logs |
bool |
false |
- |
Capture GTK and WebKit logs for debugging. |
Appearance
| Key |
Type |
Default |
Description |
appearance.sans_font |
string |
"Fira Sans" |
Sans-serif font |
appearance.serif_font |
string |
"Fira Sans" |
Serif font |
appearance.monospace_font |
string |
"Fira Code" |
Monospace font |
appearance.default_font_size |
int |
16 |
Font size in points |
appearance.color_scheme |
string |
"default" |
prefer-dark, prefer-light, default |
appearance.external_theme.enabled |
bool |
false |
Enable an external palette source |
appearance.external_theme.provider |
string |
"noctalia" |
External provider. Only noctalia is supported |
appearance.external_theme.format |
string |
"colors-json" |
External file format: colors-json or dumber-json |
appearance.external_theme.path |
string |
$XDG_CONFIG_HOME/noctalia/colors.json for colors-json; built-in dumber-json template path for dumber-json |
Path to the external theme JSON file |
Color Palettes
Light palette:
[appearance.light_palette]
background = "#fafafa"
surface = "#f4f4f5"
surface_variant = "#e4e4e7"
text = "#18181b"
muted = "#71717a"
accent = "#22c55e"
border = "#d4d4d8"
Dark palette:
[appearance.dark_palette]
background = "#0a0a0b"
surface = "#18181b"
surface_variant = "#27272a"
text = "#fafafa"
muted = "#a1a1aa"
accent = "#4ade80"
border = "#3f3f46"
External Theme: Noctalia
Dumber can integrate with Noctalia by reading the native Noctalia palette file directly. No user template is required for the default integration.
[appearance.external_theme]
enabled = true
provider = "noctalia"
format = "colors-json" # default
# Optional. Defaults to $XDG_CONFIG_HOME/noctalia/colors.json.
path = "~/.config/noctalia/colors.json"
The external file overrides palette colors only. Fonts, UI scale, pane-mode colors, and other appearance settings still come from Dumber config.
colors-json contract
colors-json reads Noctalia's active colors.json palette. Example file: docs/examples/noctalia-colors.json.
Dumber maps Noctalia roles like this:
| Noctalia key |
Dumber palette field |
mShadow |
background when present, otherwise mSurface |
mSurface |
surface |
mHover |
surface_variant when present, otherwise mSurfaceVariant |
mOnSurface |
text |
mOnSurfaceVariant |
muted |
mPrimary |
accent |
mOutline |
border |
Every mapped value must be a CSS-safe 6-digit hex color such as #aabbcc.
mShadow and mHover are optional nuance roles. When present, they are validated and used to preserve Dumber's background/surface/input contrast.
Because Noctalia's native file contains the active palette only, Dumber applies that mapped palette to both resolved light and dark palettes so the app follows the file immediately.
Advanced: dumber-json user template
dumber-json remains supported for an explicit Dumber-specific Noctalia user template:
[appearance.external_theme]
enabled = true
provider = "noctalia"
format = "dumber-json"
# Optional for dumber-json. Defaults to $XDG_CONFIG_HOME/dumber/noctalia-theme.json.
path = "~/.config/dumber/noctalia-theme.json"
The file must be JSON with root light and dark objects. Optional source, name, and mode metadata fields are accepted; name or source is used only as source metadata. Dumber chooses the active palette from appearance.color_scheme and the system color-scheme preference.
Supported palette fields are:
background
surface
surface_variant
text
muted
accent
border
Each non-empty palette value must be a CSS-safe 6-digit hex color such as #AABBCC. Empty or omitted palette fields inherit from the configured Dumber palette for that mode.
Example rendered file: docs/examples/noctalia-dumber-theme.json. Example template: docs/examples/noctalia-dumber-theme.template.json.
When Noctalia rewrites the watched file, Dumber reapplies the same resolved theme path used by config reloads. A malformed or temporarily missing file keeps the last valid external palette when one exists; disabling appearance.external_theme.enabled clears that last-good external palette and returns to Dumber's configured palettes.
Debug Options
| Key |
Type |
Default |
Description |
debug.enable_devtools |
bool |
true |
Enable browser developer tools (F12, Inspect Element) |
engine.cef.log_file |
string |
"" |
CEF runtime log path |
engine.cef.log_severity |
int32 |
0 |
CEF log severity (0, 1, 2, 3, 4, 99) |
engine.cef.trace_handlers |
bool |
false |
Log CEF handler dispatch details |
engine.cef.enable_audio_handler |
bool |
true |
Enable the experimental CEF audio handler |
Privacy
| Key |
Type |
Default |
Valid Values |
Description |
engine.cookie_policy |
string |
"always" |
always, no_third_party, never |
Cookie acceptance policy |
engine.webkit.itp_enabled |
bool |
true |
- |
Enable WebKit fallback Intelligent Tracking Prevention |
Rendering, UI Scale & Zoom
CEF is the default browser engine. WebKitGTK remains available as a fallback via engine.type = "webkit"; engine.webkit.* settings only affect that fallback engine.
| Key |
Type |
Default |
Valid Values |
Description |
engine.type |
string |
"cef" |
cef, webkit |
Browser engine selection; WebKitGTK is the fallback option |
engine.cef.render_stack |
string |
"vulkan" |
vulkan, egl |
CEF GPU render stack |
engine.cef.adaptive_windowless_frame_rate |
bool |
true |
- |
Enable adaptive CEF OSR FPS polling when windowless_frame_rate = 0 |
engine.cef.windowless_frame_rate |
int32 |
0 |
>= 0 |
Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled |
engine.cef.windowless_frame_rate_max |
int32 |
240 |
>= 0 |
Hard cap for adaptive CEF OSR FPS; 0 uses the built-in cap |
engine.cef.input.scroll_wheel_multiplier |
float |
1.0 |
> 0 |
Mouse wheel scroll sensitivity multiplier |
engine.cef.input.scroll_precise_multiplier |
float |
2.5 |
> 0 |
Precise/surface scroll sensitivity multiplier for touchpads and high-resolution wheels |
engine.cef.input.scroll_horizontal_multiplier |
float |
1.0 |
> 0 |
Horizontal scroll sensitivity multiplier |
engine.cef.input.scroll_vertical_multiplier |
float |
1.0 |
> 0 |
Vertical scroll sensitivity multiplier; combines with scroll_precise_multiplier for touchpads |
engine.cef.input.scroll_max_delta |
int32 |
0 |
>= 0 |
Maximum absolute scroll delta after scaling; 0 disables clamping |
engine.cef.input.scroll_touchpad_inertia |
bool |
true |
- |
Direct touchpad tracking with exponential release decay |
engine.cef.input.scroll_wheel_smoothing |
bool |
true |
- |
Interpolate accepted wheel impulses across frames without a long coast |
engine.cef.input.touchpad_navigation_enabled |
bool |
true |
- |
Enable two-finger touchpad swipe back/forward navigation |
engine.cef.input.touchpad_navigation_min_delta |
float |
320.0 |
> 0 |
Minimum accumulated horizontal swipe delta required for navigation |
engine.cef.input.touchpad_navigation_max_vertical_ratio |
float |
0.5 |
> 0 |
Maximum vertical-to-horizontal delta ratio allowed for navigation swipes |
engine.webkit.gsk_renderer |
string |
"auto" |
auto, opengl, vulkan, cairo |
WebKit fallback GTK renderer selection (GSK_RENDERER) |
engine.webkit.disable_dmabuf_renderer |
bool |
false |
- |
Disable WebKit fallback DMA-BUF renderer |
engine.webkit.force_compositing_mode |
bool |
false |
- |
Force WebKit fallback compositing mode (WEBKIT_FORCE_COMPOSITING_MODE) |
engine.webkit.disable_compositing_mode |
bool |
false |
- |
Disable WebKit fallback compositing mode (WEBKIT_DISABLE_COMPOSITING_MODE) |
engine.webkit.disable_mipmaps |
bool |
false |
- |
Disable GTK mipmaps for the WebKit fallback (GSK_GPU_DISABLE=mipmap) |
engine.webkit.prefer_gl |
bool |
false |
- |
Prefer OpenGL over GLES for the WebKit fallback (GDK_DEBUG=gl-prefer-gl) |
engine.webkit.draw_compositing_indicators |
bool |
false |
- |
Draw WebKit fallback compositing indicators (debug) |
engine.webkit.show_fps |
bool |
false |
- |
Show WebKit fallback FPS counter (WEBKIT_SHOW_FPS) |
engine.webkit.sample_memory |
bool |
false |
- |
Enable WebKit fallback memory sampling (WEBKIT_SAMPLE_MEMORY) |
engine.webkit.debug_frames |
bool |
false |
- |
Enable GTK frame timing debug for the WebKit fallback (GDK_DEBUG=frames) |
engine.webkit.force_vsync |
bool |
false |
- |
Force VSync for WebKit fallback video playback |
engine.webkit.gl_rendering_mode |
string |
"auto" |
auto, gles2, gl3, none |
WebKit fallback GStreamer/OpenGL API selection |
engine.webkit.gstreamer_debug_level |
int |
0 |
0-5 |
WebKit fallback GStreamer debug verbosity |
default_ui_scale |
float |
1.0 |
> 0 |
GTK widget UI scale (1.0=100%, 2.0=200%) |
default_webpage_zoom |
float |
1.2 |
> 0 |
Default page zoom (1.0=100%, 1.2=120%) |
engine.cef.input.scroll_precise_multiplier controls touchpad/high-resolution wheel scroll speed, and engine.cef.input.scroll_vertical_multiplier applies an additional vertical-only scale. engine.cef.input.touchpad_navigation_max_vertical_ratio only filters horizontal back/forward swipe recognition; it does not tune vertical scroll speed.
With the CEF engine, default_ui_scale sizes Dumber's own interface—toolbars and modals—and controls the density at which the off-screen bridge renders. It does not resize web content, so the page keeps one CSS pixel per GTK logical pixel on every output scale. Use default_webpage_zoom or per-site zoom to make pages larger or smaller.
engine.cef.input.touchpad_navigation_min_delta uses raw GTK touchpad surface units for back/forward gestures. The default 320.0 matches WebKit-style commit distance to reduce accidental navigation; raise or lower it in config.toml to tune gesture sensitivity.
Legacy key migration
Existing configs using older keys are migrated to the current engine shape. New configs should use the canonical keys above.
| Legacy key |
Current key |
privacy.cookie_policy |
engine.cookie_policy |
privacy.itp_enabled |
engine.webkit.itp_enabled |
rendering.disable_dmabuf_renderer |
engine.webkit.disable_dmabuf_renderer |
rendering.force_compositing_mode |
engine.webkit.force_compositing_mode |
rendering.disable_compositing_mode |
engine.webkit.disable_compositing_mode |
rendering.gsk_renderer |
engine.webkit.gsk_renderer |
rendering.disable_mipmaps |
engine.webkit.disable_mipmaps |
rendering.prefer_gl |
engine.webkit.prefer_gl |
rendering.draw_compositing_indicators |
engine.webkit.draw_compositing_indicators |
rendering.show_fps |
engine.webkit.show_fps |
rendering.sample_memory |
engine.webkit.sample_memory |
rendering.debug_frames |
engine.webkit.debug_frames |
media.force_vsync |
engine.webkit.force_vsync |
media.gl_rendering_mode |
engine.webkit.gl_rendering_mode |
media.gstreamer_debug_level |
engine.webkit.gstreamer_debug_level |
runtime.prefix |
engine.webkit.prefix |
performance.* |
engine.profile, engine.pool_prewarm_count, engine.zoom_cache_size, or matching engine.webkit.* custom tuning keys |
rendering.mode |
Dropped; no current setting |
Workspace Configuration
General
| Key |
Type |
Default |
Description |
workspace.new_pane_url |
string |
"about:blank" |
URL loaded for new panes/tabs (supports http(s)://, dumb://, file://, about:) |
workspace.switch_to_tab_on_move |
bool |
true |
When moving a pane to another tab, automatically switch to the destination tab |
Example:
[workspace]
new_pane_url = "dumb://history"
Action Maps
Every action map (workspace.shortcuts.actions, workspace.prefix_mode.actions, workspace.pane_mode.actions, workspace.tab_mode.actions, workspace.vim_mode.actions, workspace.resize_mode.actions, session.session_mode.actions) is completed with the default actions your config does not define, so new default actions are active without editing the file. Your bindings win: a default action is skipped when one of its keys is already used by an action you configured in the same map. Set keys = [] on an action to disable it. Keys are compared by the binding they resolve to, not by spelling: right and arrowright, ctrl and control, and M and shift+m are the same key. Action names use hyphens (stack-pane); older underscore spellings (stack_pane) are still read and renamed to the hyphen form when the config loads (if both spellings are present, the hyphenated one is used).
Pane Mode
| Key |
Type |
Default |
Description |
workspace.pane_mode.activation_shortcut |
string |
"ctrl+p" |
Pane mode activation key |
workspace.pane_mode.timeout_ms |
int |
3000 |
Pane mode timeout (ms) |
workspace.pane_mode.actions |
map |
See below |
Action→keys mappings |
Default pane mode actions:
[workspace.pane_mode.actions]
split-right = ["arrowright", "r"]
split-left = ["arrowleft", "l"]
split-up = ["arrowup", "u"]
split-down = ["arrowdown", "d"]
stack-pane = ["s"]
close-pane = ["x"]
move-pane-to-tab = ["m"]
move-pane-to-next-tab = ["M", "shift+m"]
eject-pane-to-window = ["w"]
# Consume-or-expel (niri-style) - very alpha
consume-or-expel-left = ["["]
consume-or-expel-right = ["]"]
consume-or-expel-up = ["{"]
consume-or-expel-down = ["}"]
focus-right = ["shift+arrowright", "shift+l"]
focus-left = ["shift+arrowleft", "shift+h"]
focus-up = ["shift+arrowup", "shift+k"]
focus-down = ["shift+arrowdown", "shift+j"]
confirm = ["enter"]
cancel = ["escape"]
Note: Actions are inverted to key→action map in memory for O(1) lookup performance during navigation.
Tab Mode
| Key |
Type |
Default |
Description |
workspace.tab_mode.activation_shortcut |
string |
"ctrl+t" |
Tab mode activation key |
workspace.tab_mode.timeout_ms |
int |
3000 |
Tab mode timeout (ms) |
workspace.tab_mode.actions |
map |
See below |
Action→keys mappings |
Default tab mode actions:
[workspace.tab_mode.actions]
new-tab = ["n", "c"]
close-tab = ["x"]
next-tab = ["l", "tab"]
previous-tab = ["h", "shift+tab"]
rename-tab = ["r"]
confirm = ["enter"]
cancel = ["escape"]
Note: Actions are inverted to key→action map in memory for O(1) lookup performance during navigation.
Vim Mode
| Key |
Type |
Default |
Description |
workspace.vim_mode.activation_shortcut |
string |
"ctrl+y" |
Vim mode activation key |
workspace.vim_mode.timeout_ms |
int |
0 |
Vim mode timeout in milliseconds (0 disables auto-timeout) |
workspace.vim_mode.sequence_timeout_ms |
int |
500 |
Timeout for ambiguous multi-key Vim sequences |
workspace.vim_mode.preload_accessibility |
bool |
false |
Pre-enable browser accessibility when a WebView is shown |
workspace.vim_mode.actions |
map |
See below |
Action→binding mappings |
Default vim mode actions:
[workspace.vim_mode.actions.vim-scroll-left]
keys = ["h"]
desc = "Scroll page left"
[workspace.vim_mode.actions.vim-scroll-down]
keys = ["j"]
desc = "Scroll page down"
[workspace.vim_mode.actions.vim-scroll-up]
keys = ["k"]
desc = "Scroll page up"
[workspace.vim_mode.actions.vim-scroll-right]
keys = ["l"]
desc = "Scroll page right"
[workspace.vim_mode.actions.vim-scroll-down-fast]
keys = ["shift+j"]
desc = "Scroll page down fast"
[workspace.vim_mode.actions.vim-scroll-up-fast]
keys = ["shift+k"]
desc = "Scroll page up fast"
[workspace.vim_mode.actions.focus-input]
keys = ["gi"]
desc = "Focus next page input"
[workspace.vim_mode.actions.heading-next]
keys = ["]]"]
desc = "Jump to next heading"
[workspace.vim_mode.actions.heading-prev]
keys = ["[["]
desc = "Jump to previous heading"
[workspace.vim_mode.actions.hint-follow]
keys = ["f"]
desc = "Show hints and follow a link or control"
[workspace.vim_mode.actions.hint-follow-new]
keys = ["F"]
desc = "Show hints and open a link in a new pane"
[workspace.vim_mode.actions.hint-yank-url]
keys = ["yf"]
desc = "Show hints and yank a link URL"
[workspace.vim_mode.actions.visual]
keys = ["v"]
desc = "Start visual selection (motions extend, y yanks)"
[workspace.vim_mode.actions.yank-section]
keys = ["yah"]
desc = "Yank current section"
[workspace.vim_mode.actions.yank-paragraph]
keys = ["yap"]
desc = "Yank first visible paragraph"
[workspace.vim_mode.actions.yank-code]
keys = ["yac"]
desc = "Yank first visible code block"
[workspace.vim_mode.actions.yank-table]
keys = ["yat"]
desc = "Yank first visible table"
[workspace.vim_mode.actions.yank-list]
keys = ["yal"]
desc = "Yank first visible list"
[workspace.vim_mode.actions.confirm]
keys = ["enter"]
desc = "Open selected heading link and exit mode"
[workspace.vim_mode.actions.cancel]
keys = ["escape"]
desc = "Cancel/exit mode"
Notes:
- Vim Mode scrolls and navigates the active webpage inside the active pane only.
gi focuses the next visible editable page input, starting at the first when no input is active and wrapping after the last.
]] and [[ move through visible h1–h6 headings and use the current Dumber theme accent for the selected-heading outline.
confirm opens the first link inside the selected heading, or a link wrapping that heading, then exits Vim Mode. If the selected heading has no link, it only exits the mode.
- The default
timeout_ms = 0 means Vim Mode stays active until you exit it or focus moves into an editable/browser UI context. Ctrl+Y can still activate it when a page input is already focused.
- Arrow keys still use the browser engine's native page-navigation path while Vim Mode is active.
f, F, and yf label visible links; yah, yap, yac, yat, and yal copy a section, paragraph, code block, table, or list; v starts caret and visual selection. While a hint or visual interaction runs, the page owns every key except the activation shortcut (activation_shortcut, Ctrl+Y by default), which always leaves Vim Mode and ends the interaction, whatever the page does. These actions use the CEF engine path.
Tab and Shift+Tab keep focus traversal inside the page while a page input is focused.
- Live input, heading, heading-link activation, and page-focus navigation currently use the CEF engine path; WebKit fallback supports the Vim scroll commands while equivalent semantic navigation is pending.
- Other app-level shortcuts stay suspended until Vim Mode exits, except for the Vim Mode toggle itself.
workspace.styling.vim_mode_color colors the Vim frame; workspace.styling.transition_duration controls its pulse. The mode legend and toast use the workspace styling options below.
- Scroll execution: CEF and WebKit both use the shared
BuildPageScrollByJS resolver when the page is ready. Each step starts under the viewport center, walks up through ancestors that can move in the requested direction, and hands scrolling to the document when a nested container reaches its boundary. The application repeater owns held-key cadence; each engine executes one immediate scroll step per tick. Cross-origin frame contents remain best-effort. CEF may use native precision-wheel input only as a pre-frame fallback before the browser/main frame is ready.
Resize Mode
| Key |
Type |
Default |
Description |
workspace.resize_mode.activation_shortcut |
string |
"ctrl+n" |
Resize mode activation key |
workspace.resize_mode.timeout_ms |
int |
3000 |
Resize mode timeout (ms) |
workspace.resize_mode.step_percent |
float |
5.0 |
Split ratio step per keystroke (percent) |
workspace.resize_mode.min_pane_percent |
float |
10.0 |
Minimum pane size (percent) |
workspace.resize_mode.actions |
map |
See below |
Action→keys mappings |
Default resize mode actions:
[workspace.resize_mode.actions]
resize-increase-left = ["h", "arrowleft"]
resize-increase-down = ["j", "arrowdown"]
resize-increase-up = ["k", "arrowup"]
resize-increase-right = ["l", "arrowright"]
resize-decrease-left = ["H"]
resize-decrease-down = ["J"]
resize-decrease-up = ["K"]
resize-decrease-right = ["L"]
resize-increase = ["+", "="]
resize-decrease = ["-"]
confirm = ["enter"]
cancel = ["escape"]
Notes:
- Directional actions (
resize-increase-*/resize-decrease-*) move the split divider.
- Smart actions (
resize-increase / resize-decrease) grow/shrink the active pane (best-effort) by picking a direction automatically.
- Timeout is refreshed on each resize keypress so you can keep adjusting without re-entering the mode.
Global Shortcuts
Global shortcuts are configured under workspace.shortcuts.actions using the same ActionBinding structure as modal keybindings:
| Action |
Default Keys |
Description |
toggle-floating-pane |
alt+f |
Toggle the workspace floating pane (persistent session) |
close-pane |
ctrl+w |
Close active pane (or release floating pane when floating is active) |
next-tab |
ctrl+tab |
Switch to next tab |
previous-tab |
ctrl+shift+tab |
Switch to previous tab |
consume-or-expel-left |
alt+[ |
Consume into left sibling stack, or expel left if stacked |
consume-or-expel-right |
alt+] |
Consume into right sibling stack, or expel right if stacked |
consume-or-expel-up |
alt+{ |
Consume into upper sibling stack, or expel up if stacked |
consume-or-expel-down |
alt+} |
Consume into lower sibling stack, or expel down if stacked |
Example:
[workspace.shortcuts.actions.close-pane]
keys = ["ctrl+w"]
desc = "Close active pane"
[workspace.shortcuts.actions.next-tab]
keys = ["ctrl+tab", "alt+l"]
desc = "Switch to next tab"
[workspace.shortcuts.actions.toggle-floating-pane]
keys = ["alt+f"]
desc = "Toggle floating pane"
Tip: Use dumb://config → Keybindings tab to edit shortcuts visually.
Note: New tab creation uses modal tab mode (Ctrl+T then n/c). This follows the Zellij-style modal keyboard interface.
Note: Consume-or-expel is experimental and may change behavior between releases.
Warning: Some Alt+<key> shortcuts may conflict with WebKit defaults, website handlers, or your desktop environment.
If a binding does not fire, rebind it to a different key.
Floating Pane
The floating workspace pane is a persistent overlay session: hiding it does not destroy its web content.
- Default behavior includes exactly one floating shortcut:
Alt+F (toggle-floating-pane).
- URL shortcuts (for example
Alt+G) are user-defined via workspace.floating_pane.profiles and are empty by default.
Alt+F toggles visibility only and preserves floating state.
Ctrl+W on an active floating pane closes and releases that floating session for a fresh next open.
- Floating profile keybindings support modifier combos with
ctrl, shift, and alt (for example ctrl+shift+y or ctrl+alt+m).
| Key |
Type |
Default |
Description |
workspace.floating_pane.width_pct |
float |
0.82 |
Floating pane width as fraction of workspace width ((0,1]) |
workspace.floating_pane.height_pct |
float |
0.72 |
Floating pane height as fraction of workspace height ((0,1]) |
workspace.floating_pane.profiles |
map |
{} |
Named URL profiles, each with keys[], url, optional desc |
Example:
[workspace.shortcuts.actions.toggle-floating-pane]
keys = ["alt+f"]
desc = "Toggle floating pane"
[workspace.floating_pane]
width_pct = 0.82
height_pct = 0.72
[workspace.floating_pane.profiles]
# Empty by default. Add as many entries as needed.
[workspace.floating_pane.profiles.google]
keys = ["alt+g"]
url = "https://google.com"
desc = "Open floating pane on Google"
[workspace.floating_pane.profiles.github]
keys = ["alt+h"]
url = "https://github.com"
desc = "Open floating pane on GitHub"
Browsing Context Behavior
| Key |
Type |
Default |
Valid Values |
Description |
workspace.browsing_contexts.behavior |
string |
"split" |
split, stacked, tabbed, windowed |
Placement mode for script-opened browsing contexts |
workspace.browsing_contexts.placement |
string |
"right" |
right, left, top, bottom |
Split direction when behavior is split |
workspace.browsing_contexts.open_in_new_pane |
bool |
true |
- |
Allow new browsing contexts to open in the workspace |
workspace.browsing_contexts.follow_pane_context |
bool |
true |
- |
Keep new browsing contexts aligned with the parent pane context |
workspace.browsing_contexts.blank_target_behavior |
string |
"stacked" |
split, stacked, tabbed |
Placement mode for _blank / new-page link contexts |
workspace.browsing_contexts.enable_smart_detection |
bool |
true |
- |
Use window properties to refine browsing-context classification |
workspace.browsing_contexts.oauth_auto_close |
bool |
true |
- |
Auto-close OAuth browsing contexts after success |
Workspace Styling
| Key |
Type |
Default |
Description |
workspace.styling.border_width |
int |
1 |
Active pane border width (px) - overlay |
workspace.styling.border_color |
string |
"@theme_selected_bg_color" |
Active pane border color |
workspace.styling.mode_border_width |
int |
4 |
Reserved; mode frame borders currently use fixed em widths (Vim is thinner) |
workspace.styling.pane_mode_color |
string |
"#4A90E2" |
Pane mode color (blue) |
workspace.styling.tab_mode_color |
string |
"#FFA500" |
Tab mode color (orange) - used for border and toaster |
workspace.styling.session_mode_color |
string |
"#9B59B6" |
Session mode color (purple) - used for border and toaster |
workspace.styling.vim_mode_color |
string |
"#F7768E" |
Vim mode border, pulse, toast, and legend color |
workspace.styling.resize_mode_color |
string |
"#00D4AA" |
Resize mode color (teal) - used for border and toaster |
workspace.styling.mode_indicator_toaster_enabled |
bool |
true |
Show the bottom-left mode toaster header while a mode is active. When false and mode_legend is not off, the header is still shown because the legend expands from it |
workspace.styling.mode_legend |
string |
"delay" |
always, delay, or off; the legend expands from the bottom-left mode toaster in that mode's color. off keeps only the toaster header, without the expand/collapse chevron |
workspace.styling.mode_legend_delay_ms |
int |
500 |
Wait before showing the expanded legend when set to delay (>= 0) |
workspace.styling.mode_legend_linger |
bool |
true |
Keep the legend visible, read-only, after its mode ends while the pointer is over it, until the next key press or click. |
workspace.styling.mode_legend_animations |
bool |
true |
Animate the legend and its keycap feedback |
workspace.styling.mode_legend_collapsed |
bool |
false |
Collapse the legend to the mode toaster header. Clicking the toaster toggles it, applies immediately in every window, and is saved here for all modes |
workspace.styling.transition_duration |
int |
120 |
Base Vim border pulse duration (ms); legend appearance is 120 ms |
Example:
[workspace.styling]
border_width = 1
border_color = "@theme_selected_bg_color"
mode_border_width = 4
pane_mode_color = "#4A90E2" # Blue for pane mode
tab_mode_color = "#FFA500" # Orange for tab mode
session_mode_color = "#9B59B6" # Purple for session mode
vim_mode_color = "#F7768E" # Vim mode
resize_mode_color = "#00D4AA" # Teal for resize mode
mode_indicator_toaster_enabled = true
mode_legend = "delay"
mode_legend_delay_ms = 500
mode_legend_linger = true
mode_legend_animations = true
mode_legend_collapsed = false
transition_duration = 120
Session
| Key |
Type |
Default |
Description |
session.auto_restore |
bool |
false |
Automatically restore the last session on startup |
session.snapshot_interval_ms |
int |
5000 |
Minimum interval between snapshots in milliseconds |
session.max_exited_sessions |
int |
50 |
Maximum number of exited sessions to keep |
session.max_exited_session_age_days |
int |
7 |
Maximum age in days for exited sessions (auto-deleted on startup) |
Session Mode
| Key |
Type |
Default |
Description |
session.session_mode.activation_shortcut |
string |
"ctrl+o" |
Session mode activation key |
session.session_mode.timeout_ms |
int |
3000 |
Session mode timeout (ms) |
session.session_mode.actions |
map |
See below |
Action to keys mappings |
Default session mode actions:
[session.session_mode.actions]
session-manager = ["s", "w"]
confirm = ["enter"]
cancel = ["escape"]
Example:
[session]
auto_restore = false # Don't auto-restore on startup
snapshot_interval_ms = 5000 # Save state every 5 seconds (debounced)
max_exited_sessions = 50 # Keep last 50 exited sessions
max_exited_session_age_days = 7 # Delete sessions older than 7 days on startup
| Key |
Type |
Default |
Valid Values |
Description |
media.hardware_decoding |
string |
"auto" |
auto, force, disable |
Hardware video decoding mode |
media.prefer_av1 |
bool |
false |
- |
Prefer AV1 codec when available |
media.show_diagnostics |
bool |
false |
- |
Show media diagnostics warnings at startup |
WebKit fallback GStreamer tuning is configured under engine.webkit.force_vsync, engine.webkit.gl_rendering_mode, and engine.webkit.gstreamer_debug_level.
Hardware decoding modes:
auto (recommended): Hardware preferred with software fallback - fixes Twitch Error #4000
force: Hardware only - fails if unavailable
disable: Software only - higher CPU usage
GPU auto-detection:
Dumber automatically detects your GPU vendor (AMD/Intel/NVIDIA) and sets optimal VA-API driver settings:
- AMD: Uses
radeonsi driver
- Intel: Uses
iHD driver (modern, for Broadwell+)
- NVIDIA: Uses
nvidia driver with EGL platform
Example:
[media]
hardware_decoding = "auto" # HW preferred, SW fallback
prefer_av1 = false # Let site choose codec
show_diagnostics = false # Keep off for daily use
WebKit fallback GStreamer diagnostics:
[engine.webkit]
gstreamer_debug_level = 3
Set media.show_diagnostics = false and engine.webkit.gstreamer_debug_level = 0 to return to normal mode.
Diagnostics CLI:
# Check GStreamer plugins and VA-API status
dumber doctor --media
Runtime
| Key |
Type |
Default |
Description |
engine.cef.cef_dir |
string |
"" |
Optional CEF runtime directory containing libcef.so and resources |
engine.webkit.prefix |
string |
"" |
Optional WebKitGTK fallback runtime prefix (e.g., /opt/webkitgtk) |
engine.cef.cef_dir overrides CEF runtime discovery. For the WebKitGTK fallback, engine.webkit.prefix prepends prefix-derived paths to:
PKG_CONFIG_PATH (for version checks)
LD_LIBRARY_PATH (for runtime library loading)
GI_TYPELIB_PATH (GObject introspection)
XDG_DATA_DIRS (schemas/resources)
Example:
[engine.cef]
cef_dir = "/opt/cef"
[engine.webkit]
prefix = "/opt/webkitgtk"
Clipboard
| Key |
Type |
Default |
Description |
clipboard.auto_copy_on_selection |
bool |
true |
Automatically copy selected text to clipboard (zellij/tmux-style) |
When enabled, selecting text in a web page immediately copies it to the clipboard with a brief toast notification. Does not apply to text selection in input fields or textareas.
Example:
[clipboard]
auto_copy_on_selection = true # Enabled by default
Content Filtering
| Key |
Type |
Default |
Description |
content_filtering.enabled |
bool |
true |
Enable ad blocking |
content_filtering.auto_update |
bool |
true |
Automatically update filters |
Notes:
- Filter data is downloaded from
bnema/ublock-webkit-filters GitHub releases.
- Domain whitelist is managed via the database (
content_whitelist table), not the config file.
Update
| Key |
Type |
Default |
Description |
update.enable_on_startup |
bool |
true |
Check for updates when browser starts |
update.auto_download |
bool |
false |
Automatically download updates in background |
update.notify_on_new_settings |
bool |
true |
Show toast notification when new config settings are available |
Example:
[update]
enable_on_startup = true # Check for updates on startup
auto_download = false # Don't auto-download (prompt instead)
notify_on_new_settings = true # Show toast when config migration available
CLI Commands:
# Check config status and available migrations
dumber config status
# Add missing settings with default values
dumber config migrate
# Skip confirmation prompt
dumber config migrate --yes
Performance profiles tune engine resource behavior. CEF is the default engine; the engine.webkit.* custom fields below apply only when using the WebKitGTK fallback.
Note: Performance settings are applied at browser startup. Changes require a restart to take effect.
| Key |
Type |
Default |
Valid Values |
Description |
engine.profile |
string |
"default" |
default, lite, balanced, max, custom |
Performance profile selection |
engine.pool_prewarm_count |
int |
4 |
>= 0 |
WebViews to pre-create at startup |
engine.zoom_cache_size |
int |
256 |
>= 0 |
Domain zoom levels to cache |
Profiles
| Profile |
Description |
Use Case |
default |
No tuning, uses engine defaults |
Compatibility baseline, troubleshooting |
lite |
Reduced resource usage |
Low-RAM systems (< 4GB), battery saving |
balanced |
Moderate tuning |
General use |
max |
Maximum responsiveness |
Heavy pages (GitHub PRs, complex SPAs), high-end systems |
custom |
Manual control over all settings |
Advanced users who want fine-grained control |
Example:
[engine]
profile = "default"
pool_prewarm_count = 4
zoom_cache_size = 256
WebKit fallback custom profile settings
When engine.profile = "custom" and engine.type = "webkit", you can configure individual WebKit tuning options:
| Key |
Type |
Default |
Description |
engine.webkit.skia_cpu_painting_threads |
int |
0 |
Skia CPU rendering threads (0=unset) |
engine.webkit.skia_gpu_painting_threads |
int |
-1 |
Skia GPU rendering threads (-1=unset, 0=disable) |
engine.webkit.skia_enable_cpu_rendering |
bool |
false |
Force CPU rendering |
engine.webkit.web_process_memory_limit_mb |
int |
0 |
Web process memory limit in MB (0=unset) |
engine.webkit.web_process_memory_poll_interval_sec |
float |
0 |
Memory check interval (0=WebKit default: 30s) |
engine.webkit.web_process_memory_conservative_threshold |
float |
0 |
Conservative cleanup threshold (0=unset) |
engine.webkit.web_process_memory_strict_threshold |
float |
0 |
Strict cleanup threshold (0=unset) |
engine.webkit.network_process_memory_limit_mb |
int |
0 |
Network process memory limit in MB |
engine.webkit.network_process_memory_poll_interval_sec |
float |
0 |
Network memory check interval |
engine.webkit.network_process_memory_conservative_threshold |
float |
0 |
Network conservative threshold |
engine.webkit.network_process_memory_strict_threshold |
float |
0 |
Network strict threshold |
Custom WebKit fallback example:
[engine]
type = "webkit"
profile = "custom"
pool_prewarm_count = 6
zoom_cache_size = 256
[engine.webkit]
skia_cpu_painting_threads = 4
skia_gpu_painting_threads = 2
web_process_memory_limit_mb = 1024
web_process_memory_conservative_threshold = 0.4
web_process_memory_strict_threshold = 0.6
Important: Individual engine.webkit.* tuning fields are ignored unless engine.profile = "custom".
Downloads
| Key |
Type |
Default |
Description |
downloads.path |
string |
"" |
Download directory path (empty = $XDG_DOWNLOAD_DIR or ~/Downloads) |
Downloads are saved to the configured directory with toast notifications for download started, completed, and failed events.
Example:
[downloads]
path = "" # Use system default ($XDG_DOWNLOAD_DIR or ~/Downloads)
# Or specify a custom directory:
# path = "/home/user/my-downloads"
Environment Variables
All config values can be overridden via environment variables with the prefix DUMBER_:
# Database
DUMBER_DATABASE_PATH=/custom/path/db.sqlite
# Engine/rendering (examples)
DUMBER_ENGINE_TYPE=cef
# CEF defaults to the GPU-first Vulkan DMABUF stack.
# Use EGL/OpenGL for driver compatibility:
# DUMBER_ENGINE_CEF_RENDER_STACK=egl
# WebKit fallback rendering overrides:
# DUMBER_ENGINE_WEBKIT_DISABLE_DMABUF_RENDERER=true
# DUMBER_ENGINE_WEBKIT_GSK_RENDERER=opengl
DUMBER_DEFAULT_WEBPAGE_ZOOM=1.5
# Logging
DUMBER_LOG_LEVEL=debug
DUMBER_LOG_FORMAT=json
source
1# Configuration Guide
2
3**Location**: `~/.config/dumber/config.toml`
4**Formats**: TOML (recommended), JSON, YAML
5
6## Database
7
8| Key | Type | Default | Description |
9|-----|------|---------|-------------|
10| `database.path` | string | `~/.local/share/dumber/dumber.db` | Database file path |
11
12## History
13
14| Key | Type | Default | Valid Values | Description |
15|-----|------|---------|--------------|-------------|
16| `history.max_entries` | int | `10000` | > 0 | Maximum number of history entries |
17| `history.retention_period_days` | int | `365` | > 0 | Days to keep history (1 year) |
18| `history.cleanup_interval_days` | int | `1` | > 0 | How often to run cleanup |
19
20## Search Configuration
21
22| Key | Type | Default | Description |
23|-----|------|---------|-------------|
24| `default_search_engine` | string | `"https://duckduckgo.com/?q=%s"` | Default search engine URL template (must contain `%s` placeholder) |
25| `search_shortcuts` | map | See defaults | Map of shortcut aliases to URLs |
26
27**Example:**
28```toml
29# Default search engine - used when no shortcut is specified
30default_search_engine = "https://duckduckgo.com/?q=%s"
31
32# Alternative search engines:
33# default_search_engine = "https://www.google.com/search?q=%s"
34# default_search_engine = "https://www.startpage.com/search?q=%s"
35# default_search_engine = "https://search.brave.com/search?q=%s"
36```
37
38**Default shortcuts (usage: `!shortcut query`):**
39```toml
40# Examples:
41# !ddg golang → DuckDuckGo search for "golang"
42# !g rust tutorial → Google search for "rust tutorial"
43# !gi cats → Google Images search for "cats"
44# !gh opencode → GitHub search for "opencode"
45# !yt music video → YouTube search for "music video"
46
47[search_shortcuts.ddg]
48url = "https://duckduckgo.com/?q=%s"
49description = "DuckDuckGo search"
50
51[search_shortcuts.g]
52url = "https://google.com/search?q=%s"
53description = "Google Search"
54
55[search_shortcuts.gi]
56url = "https://google.com/search?tbm=isch&q=%s"
57description = "Google Images"
58
59[search_shortcuts.gh]
60url = "https://github.com/search?q=%s"
61description = "GitHub"
62
63[search_shortcuts.yt]
64url = "https://youtube.com/results?search_query=%s"
65description = "YouTube"
66```
67
68## Dmenu / Launcher
69
70These settings control the `dumber dmenu` CLI command for rofi/fuzzel integration.
71
72| Key | Type | Default | Description |
73|-----|------|---------|-------------|
74| `dmenu.max_history_days` | int | `30` | Number of days of history to show (0 = all) |
75| `dmenu.show_visit_count` | bool | `true` | Show visit counts in output |
76| `dmenu.show_last_visited` | bool | `true` | Show last visited dates in output |
77| `dmenu.history_prefix` | string | `"🕒"` | Prefix for history items |
78| `dmenu.shortcut_prefix` | string | `"🔍"` | Prefix for shortcuts |
79| `dmenu.url_prefix` | string | `"🌐"` | Prefix for URLs |
80| `dmenu.date_format` | string | `"2006-01-02 15:04"` | Go time format string |
81| `dmenu.sort_by_visit_count` | bool | `true` | Sort by visit count instead of recency |
82
83**CLI Usage:**
84```bash
85# Pipe mode (default) - outputs history for rofi/fuzzel with favicons
86dumber dmenu | rofi -dmenu -show-icons -p "Browse: " | dumber dmenu --select
87dumber dmenu | fuzzel --dmenu -p "Browse: " | dumber dmenu --select
88
89# Interactive TUI mode
90dumber dmenu --interactive
91
92# Override history days via CLI flag (show last 7 days)
93dumber dmenu --days 7
94
95# Sort by most visited instead of recency
96dumber dmenu --most-visited
97
98# Combined: most visited from last 14 days
99dumber dmenu --days 14 --most-visited
100```
101
102## Omnibox
103
104| Key | Type | Default | Valid Values | Description |
105|-----|------|---------|--------------|-------------|
106| `omnibox.style` | string | `"command"` | `multiplexer`, `command`, `minimal` | Omnibox visual treatment |
107| `omnibox.initial_behavior` | string | `"recent"` | `recent`, `most_visited`, `none` | Initial history display behavior; press `Ctrl+R` in the omnibox to switch |
108| `omnibox.max_history_days` | int | `30` | `>= 0` | Days of history used by omnibox suggestions, for both typed searches and the empty-input list (`0` = all history) |
109| `omnibox.auto_open_on_new_pane` | bool | `false` | - | Automatically open the omnibox after creating a new pane |
110
111Changing `omnibox.style` takes effect after restarting Dumber.
112
113**Example:**
114```toml
115[omnibox]
116style = "command" # Soft terminal-inspired command palette
117initial_behavior = "recent" # Show recent history when omnibox opens
118max_history_days = 30 # History window for omnibox searches and initial list
119auto_open_on_new_pane = false
120
121# Alternative visual treatments:
122# style = "multiplexer" # Segmented terminal multiplexer treatment
123# style = "minimal" # Bare terminal typography and hairlines
124
125# Alternative options:
126# initial_behavior = "most_visited" # Show most visited sites
127# initial_behavior = "none" # Show no initial suggestions
128```
129
130> **Note:** `omnibox.max_history_days` is non-destructive: it never deletes history, it only limits which entries omnibox suggestions consider. `most_visited` ranks by lifetime visit counts among URLs whose `last_visited` falls inside the window.
131>
132> This key replaced `omnibox.most_visited_days`. Run `dumber config migrate` to rename the legacy key while preserving its value. The derived environment variable is `DUMBER_OMNIBOX_MAX_HISTORY_DAYS`; the old `DUMBER_OMNIBOX_MOST_VISITED_DAYS` is no longer read.
133
134## Logging
135
136| Key | Type | Default | Valid Values | Description |
137|-----|------|---------|--------------|-------------|
138| `logging.level` | string | `"info"` | `trace`, `debug`, `info`, `warn`, `error`, `fatal` | Log level |
139| `logging.format` | string | `"text"` | `text`, `json`, `console` | Log format |
140| `logging.max_age` | int | `7` | `>= 0` | Days to keep logs |
141| `logging.max_files` | int | `100` | `>= 0` | Session log files to keep (`0` disables count cleanup) |
142| `logging.log_dir` | string | `~/.local/state/dumber/logs` | any path | Log directory |
143| `logging.enable_file_log` | bool | `true` | - | Enable file logging |
144| `logging.capture_console` | bool | `false` | - | Capture browser console to logs |
145| `logging.capture_gtk_logs` | bool | `false` | - | Capture GTK and WebKit logs for debugging. |
146
147## Appearance
148
149| Key | Type | Default | Description |
150|-----|------|---------|-------------|
151| `appearance.sans_font` | string | `"Fira Sans"` | Sans-serif font |
152| `appearance.serif_font` | string | `"Fira Sans"` | Serif font |
153| `appearance.monospace_font` | string | `"Fira Code"` | Monospace font |
154| `appearance.default_font_size` | int | `16` | Font size in points |
155| `appearance.color_scheme` | string | `"default"` | `prefer-dark`, `prefer-light`, `default` |
156| `appearance.external_theme.enabled` | bool | `false` | Enable an external palette source |
157| `appearance.external_theme.provider` | string | `"noctalia"` | External provider. Only `noctalia` is supported |
158| `appearance.external_theme.format` | string | `"colors-json"` | External file format: `colors-json` or `dumber-json` |
159| `appearance.external_theme.path` | string | `$XDG_CONFIG_HOME/noctalia/colors.json` for `colors-json`; built-in dumber-json template path for `dumber-json` | Path to the external theme JSON file |
160
161### Color Palettes
162
163**Light palette:**
164
165```toml
166[appearance.light_palette]
167background = "#fafafa"
168surface = "#f4f4f5"
169surface_variant = "#e4e4e7"
170text = "#18181b"
171muted = "#71717a"
172accent = "#22c55e"
173border = "#d4d4d8"
174```
175
176**Dark palette:**
177
178```toml
179[appearance.dark_palette]
180background = "#0a0a0b"
181surface = "#18181b"
182surface_variant = "#27272a"
183text = "#fafafa"
184muted = "#a1a1aa"
185accent = "#4ade80"
186border = "#3f3f46"
187```
188
189### External Theme: Noctalia
190
191Dumber can integrate with Noctalia by reading the native Noctalia palette file directly. No user template is required for the default integration.
192
193```toml
194[appearance.external_theme]
195enabled = true
196provider = "noctalia"
197format = "colors-json" # default
198# Optional. Defaults to $XDG_CONFIG_HOME/noctalia/colors.json.
199path = "~/.config/noctalia/colors.json"
200```
201
202The external file overrides palette colors only. Fonts, UI scale, pane-mode colors, and other appearance settings still come from Dumber config.
203
204#### `colors-json` contract
205
206`colors-json` reads Noctalia's active `colors.json` palette. Example file: [`docs/examples/noctalia-colors.json`](../examples/noctalia-colors.json).
207
208Dumber maps Noctalia roles like this:
209
210| Noctalia key | Dumber palette field |
211|--------------|----------------------|
212| `mShadow` | `background` when present, otherwise `mSurface` |
213| `mSurface` | `surface` |
214| `mHover` | `surface_variant` when present, otherwise `mSurfaceVariant` |
215| `mOnSurface` | `text` |
216| `mOnSurfaceVariant` | `muted` |
217| `mPrimary` | `accent` |
218| `mOutline` | `border` |
219
220Every mapped value must be a CSS-safe 6-digit hex color such as `#aabbcc`.
221
222`mShadow` and `mHover` are optional nuance roles. When present, they are validated and used to preserve Dumber's background/surface/input contrast.
223
224Because Noctalia's native file contains the active palette only, Dumber applies that mapped palette to both resolved light and dark palettes so the app follows the file immediately.
225
226#### Advanced: `dumber-json` user template
227
228`dumber-json` remains supported for an explicit Dumber-specific Noctalia user template:
229
230```toml
231[appearance.external_theme]
232enabled = true
233provider = "noctalia"
234format = "dumber-json"
235# Optional for dumber-json. Defaults to $XDG_CONFIG_HOME/dumber/noctalia-theme.json.
236path = "~/.config/dumber/noctalia-theme.json"
237```
238
239The file must be JSON with root `light` and `dark` objects. Optional `source`, `name`, and `mode` metadata fields are accepted; `name` or `source` is used only as source metadata. Dumber chooses the active palette from `appearance.color_scheme` and the system color-scheme preference.
240
241Supported palette fields are:
242
243- `background`
244- `surface`
245- `surface_variant`
246- `text`
247- `muted`
248- `accent`
249- `border`
250
251Each non-empty palette value must be a CSS-safe 6-digit hex color such as `#AABBCC`. Empty or omitted palette fields inherit from the configured Dumber palette for that mode.
252
253Example rendered file: [`docs/examples/noctalia-dumber-theme.json`](../examples/noctalia-dumber-theme.json). Example template: [`docs/examples/noctalia-dumber-theme.template.json`](../examples/noctalia-dumber-theme.template.json).
254
255When Noctalia rewrites the watched file, Dumber reapplies the same resolved theme path used by config reloads. A malformed or temporarily missing file keeps the last valid external palette when one exists; disabling `appearance.external_theme.enabled` clears that last-good external palette and returns to Dumber's configured palettes.
256
257## Debug Options
258
259| Key | Type | Default | Description |
260|-----|------|---------|-------------|
261| `debug.enable_devtools` | bool | `true` | Enable browser developer tools (F12, Inspect Element) |
262| `engine.cef.log_file` | string | `""` | CEF runtime log path |
263| `engine.cef.log_severity` | int32 | `0` | CEF log severity (`0`, `1`, `2`, `3`, `4`, `99`) |
264| `engine.cef.trace_handlers` | bool | `false` | Log CEF handler dispatch details |
265| `engine.cef.enable_audio_handler` | bool | `true` | Enable the experimental CEF audio handler |
266
267## Privacy
268
269| Key | Type | Default | Valid Values | Description |
270|-----|------|---------|--------------|-------------|
271| `engine.cookie_policy` | string | `"always"` | `always`, `no_third_party`, `never` | Cookie acceptance policy |
272| `engine.webkit.itp_enabled` | bool | `true` | - | Enable WebKit fallback Intelligent Tracking Prevention |
273
274
275## Rendering, UI Scale & Zoom
276
277CEF is the default browser engine. WebKitGTK remains available as a fallback via `engine.type = "webkit"`; `engine.webkit.*` settings only affect that fallback engine.
278
279| Key | Type | Default | Valid Values | Description |
280|-----|------|---------|--------------|-------------|
281| `engine.type` | string | `"cef"` | `cef`, `webkit` | Browser engine selection; WebKitGTK is the fallback option |
282| `engine.cef.render_stack` | string | `"vulkan"` | `vulkan`, `egl` | CEF GPU render stack |
283| `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | - | Enable adaptive CEF OSR FPS polling when `windowless_frame_rate = 0` |
284| `engine.cef.windowless_frame_rate` | int32 | `0` | `>= 0` | Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled |
285| `engine.cef.windowless_frame_rate_max` | int32 | `240` | `>= 0` | Hard cap for adaptive CEF OSR FPS; 0 uses the built-in cap |
286| `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | `> 0` | Mouse wheel scroll sensitivity multiplier |
287| `engine.cef.input.scroll_precise_multiplier` | float | `2.5` | `> 0` | Precise/surface scroll sensitivity multiplier for touchpads and high-resolution wheels |
288| `engine.cef.input.scroll_horizontal_multiplier` | float | `1.0` | `> 0` | Horizontal scroll sensitivity multiplier |
289| `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | `> 0` | Vertical scroll sensitivity multiplier; combines with `scroll_precise_multiplier` for touchpads |
290| `engine.cef.input.scroll_max_delta` | int32 | `0` | `>= 0` | Maximum absolute scroll delta after scaling; 0 disables clamping |
291| `engine.cef.input.scroll_touchpad_inertia` | bool | `true` | - | Direct touchpad tracking with exponential release decay |
292| `engine.cef.input.scroll_wheel_smoothing` | bool | `true` | - | Interpolate accepted wheel impulses across frames without a long coast |
293| `engine.cef.input.touchpad_navigation_enabled` | bool | `true` | - | Enable two-finger touchpad swipe back/forward navigation |
294| `engine.cef.input.touchpad_navigation_min_delta` | float | `320.0` | `> 0` | Minimum accumulated horizontal swipe delta required for navigation |
295| `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | `> 0` | Maximum vertical-to-horizontal delta ratio allowed for navigation swipes |
296| `engine.webkit.gsk_renderer` | string | `"auto"` | `auto`, `opengl`, `vulkan`, `cairo` | WebKit fallback GTK renderer selection (`GSK_RENDERER`) |
297| `engine.webkit.disable_dmabuf_renderer` | bool | `false` | - | Disable WebKit fallback DMA-BUF renderer |
298| `engine.webkit.force_compositing_mode` | bool | `false` | - | Force WebKit fallback compositing mode (`WEBKIT_FORCE_COMPOSITING_MODE`) |
299| `engine.webkit.disable_compositing_mode` | bool | `false` | - | Disable WebKit fallback compositing mode (`WEBKIT_DISABLE_COMPOSITING_MODE`) |
300| `engine.webkit.disable_mipmaps` | bool | `false` | - | Disable GTK mipmaps for the WebKit fallback (`GSK_GPU_DISABLE=mipmap`) |
301| `engine.webkit.prefer_gl` | bool | `false` | - | Prefer OpenGL over GLES for the WebKit fallback (`GDK_DEBUG=gl-prefer-gl`) |
302| `engine.webkit.draw_compositing_indicators` | bool | `false` | - | Draw WebKit fallback compositing indicators (debug) |
303| `engine.webkit.show_fps` | bool | `false` | - | Show WebKit fallback FPS counter (`WEBKIT_SHOW_FPS`) |
304| `engine.webkit.sample_memory` | bool | `false` | - | Enable WebKit fallback memory sampling (`WEBKIT_SAMPLE_MEMORY`) |
305| `engine.webkit.debug_frames` | bool | `false` | - | Enable GTK frame timing debug for the WebKit fallback (`GDK_DEBUG=frames`) |
306| `engine.webkit.force_vsync` | bool | `false` | - | Force VSync for WebKit fallback video playback |
307| `engine.webkit.gl_rendering_mode` | string | `"auto"` | `auto`, `gles2`, `gl3`, `none` | WebKit fallback GStreamer/OpenGL API selection |
308| `engine.webkit.gstreamer_debug_level` | int | `0` | `0-5` | WebKit fallback GStreamer debug verbosity |
309| `default_ui_scale` | float | `1.0` | `> 0` | GTK widget UI scale (1.0=100%, 2.0=200%) |
310| `default_webpage_zoom` | float | `1.2` | `> 0` | Default page zoom (1.0=100%, 1.2=120%) |
311
312`engine.cef.input.scroll_precise_multiplier` controls touchpad/high-resolution wheel scroll speed, and `engine.cef.input.scroll_vertical_multiplier` applies an additional vertical-only scale. `engine.cef.input.touchpad_navigation_max_vertical_ratio` only filters horizontal back/forward swipe recognition; it does not tune vertical scroll speed.
313
314With the CEF engine, `default_ui_scale` sizes Dumber's own interface—toolbars and modals—and controls the density at which the off-screen bridge renders. It does not resize web content, so the page keeps one CSS pixel per GTK logical pixel on every output scale. Use `default_webpage_zoom` or per-site zoom to make pages larger or smaller.
315
316`engine.cef.input.touchpad_navigation_min_delta` uses raw GTK touchpad surface units for back/forward gestures. The default `320.0` matches WebKit-style commit distance to reduce accidental navigation; raise or lower it in `config.toml` to tune gesture sensitivity.
317
318### Legacy key migration
319
320Existing configs using older keys are migrated to the current engine shape. New configs should use the canonical keys above.
321
322| Legacy key | Current key |
323|------------|-------------|
324| `privacy.cookie_policy` | `engine.cookie_policy` |
325| `privacy.itp_enabled` | `engine.webkit.itp_enabled` |
326| `rendering.disable_dmabuf_renderer` | `engine.webkit.disable_dmabuf_renderer` |
327| `rendering.force_compositing_mode` | `engine.webkit.force_compositing_mode` |
328| `rendering.disable_compositing_mode` | `engine.webkit.disable_compositing_mode` |
329| `rendering.gsk_renderer` | `engine.webkit.gsk_renderer` |
330| `rendering.disable_mipmaps` | `engine.webkit.disable_mipmaps` |
331| `rendering.prefer_gl` | `engine.webkit.prefer_gl` |
332| `rendering.draw_compositing_indicators` | `engine.webkit.draw_compositing_indicators` |
333| `rendering.show_fps` | `engine.webkit.show_fps` |
334| `rendering.sample_memory` | `engine.webkit.sample_memory` |
335| `rendering.debug_frames` | `engine.webkit.debug_frames` |
336| `media.force_vsync` | `engine.webkit.force_vsync` |
337| `media.gl_rendering_mode` | `engine.webkit.gl_rendering_mode` |
338| `media.gstreamer_debug_level` | `engine.webkit.gstreamer_debug_level` |
339| `runtime.prefix` | `engine.webkit.prefix` |
340| `performance.*` | `engine.profile`, `engine.pool_prewarm_count`, `engine.zoom_cache_size`, or matching `engine.webkit.*` custom tuning keys |
341| `rendering.mode` | Dropped; no current setting |
342
343## Workspace Configuration
344
345### General
346
347| Key | Type | Default | Description |
348|-----|------|---------|-------------|
349| `workspace.new_pane_url` | string | `"about:blank"` | URL loaded for new panes/tabs (supports `http(s)://`, `dumb://`, `file://`, `about:`) |
350| `workspace.switch_to_tab_on_move` | bool | `true` | When moving a pane to another tab, automatically switch to the destination tab |
351
352**Example:**
353```toml
354[workspace]
355new_pane_url = "dumb://history"
356```
357
358### Action Maps
359
360Every action map (`workspace.shortcuts.actions`, `workspace.prefix_mode.actions`, `workspace.pane_mode.actions`, `workspace.tab_mode.actions`, `workspace.vim_mode.actions`, `workspace.resize_mode.actions`, `session.session_mode.actions`) is completed with the default actions your config does not define, so new default actions are active without editing the file. Your bindings win: a default action is skipped when one of its keys is already used by an action you configured in the same map. Set `keys = []` on an action to disable it. Keys are compared by the binding they resolve to, not by spelling: `right` and `arrowright`, `ctrl` and `control`, and `M` and `shift+m` are the same key. Action names use hyphens (`stack-pane`); older underscore spellings (`stack_pane`) are still read and renamed to the hyphen form when the config loads (if both spellings are present, the hyphenated one is used).
361
362### Pane Mode
363
364| Key | Type | Default | Description |
365|-----|------|---------|-------------|
366| `workspace.pane_mode.activation_shortcut` | string | `"ctrl+p"` | Pane mode activation key |
367| `workspace.pane_mode.timeout_ms` | int | `3000` | Pane mode timeout (ms) |
368| `workspace.pane_mode.actions` | map | See below | Action→keys mappings |
369
370**Default pane mode actions:**
371```toml
372[workspace.pane_mode.actions]
373split-right = ["arrowright", "r"]
374split-left = ["arrowleft", "l"]
375split-up = ["arrowup", "u"]
376split-down = ["arrowdown", "d"]
377stack-pane = ["s"]
378close-pane = ["x"]
379move-pane-to-tab = ["m"]
380move-pane-to-next-tab = ["M", "shift+m"]
381eject-pane-to-window = ["w"]
382
383# Consume-or-expel (niri-style) - very alpha
384consume-or-expel-left = ["["]
385consume-or-expel-right = ["]"]
386consume-or-expel-up = ["{"]
387consume-or-expel-down = ["}"]
388
389focus-right = ["shift+arrowright", "shift+l"]
390focus-left = ["shift+arrowleft", "shift+h"]
391focus-up = ["shift+arrowup", "shift+k"]
392focus-down = ["shift+arrowdown", "shift+j"]
393confirm = ["enter"]
394cancel = ["escape"]
395```
396
397> **Note:** Actions are inverted to key→action map in memory for O(1) lookup performance during navigation.
398
399### Tab Mode
400
401| Key | Type | Default | Description |
402|-----|------|---------|-------------|
403| `workspace.tab_mode.activation_shortcut` | string | `"ctrl+t"` | Tab mode activation key |
404| `workspace.tab_mode.timeout_ms` | int | `3000` | Tab mode timeout (ms) |
405| `workspace.tab_mode.actions` | map | See below | Action→keys mappings |
406
407**Default tab mode actions:**
408```toml
409[workspace.tab_mode.actions]
410new-tab = ["n", "c"]
411close-tab = ["x"]
412next-tab = ["l", "tab"]
413previous-tab = ["h", "shift+tab"]
414rename-tab = ["r"]
415confirm = ["enter"]
416cancel = ["escape"]
417```
418
419> **Note:** Actions are inverted to key→action map in memory for O(1) lookup performance during navigation.
420
421### Vim Mode
422
423| Key | Type | Default | Description |
424|-----|------|---------|-------------|
425| `workspace.vim_mode.activation_shortcut` | string | `"ctrl+y"` | Vim mode activation key |
426| `workspace.vim_mode.timeout_ms` | int | `0` | Vim mode timeout in milliseconds (`0` disables auto-timeout) |
427| `workspace.vim_mode.sequence_timeout_ms` | int | `500` | Timeout for ambiguous multi-key Vim sequences |
428| `workspace.vim_mode.preload_accessibility` | bool | `false` | Pre-enable browser accessibility when a WebView is shown |
429| `workspace.vim_mode.actions` | map | See below | Action→binding mappings |
430
431**Default vim mode actions:**
432
433```toml
434[workspace.vim_mode.actions.vim-scroll-left]
435keys = ["h"]
436desc = "Scroll page left"
437
438[workspace.vim_mode.actions.vim-scroll-down]
439keys = ["j"]
440desc = "Scroll page down"
441
442[workspace.vim_mode.actions.vim-scroll-up]
443keys = ["k"]
444desc = "Scroll page up"
445
446[workspace.vim_mode.actions.vim-scroll-right]
447keys = ["l"]
448desc = "Scroll page right"
449
450[workspace.vim_mode.actions.vim-scroll-down-fast]
451keys = ["shift+j"]
452desc = "Scroll page down fast"
453
454[workspace.vim_mode.actions.vim-scroll-up-fast]
455keys = ["shift+k"]
456desc = "Scroll page up fast"
457
458[workspace.vim_mode.actions.focus-input]
459keys = ["gi"]
460desc = "Focus next page input"
461
462[workspace.vim_mode.actions.heading-next]
463keys = ["]]"]
464desc = "Jump to next heading"
465
466[workspace.vim_mode.actions.heading-prev]
467keys = ["[["]
468desc = "Jump to previous heading"
469
470[workspace.vim_mode.actions.hint-follow]
471keys = ["f"]
472desc = "Show hints and follow a link or control"
473
474[workspace.vim_mode.actions.hint-follow-new]
475keys = ["F"]
476desc = "Show hints and open a link in a new pane"
477
478[workspace.vim_mode.actions.hint-yank-url]
479keys = ["yf"]
480desc = "Show hints and yank a link URL"
481
482[workspace.vim_mode.actions.visual]
483keys = ["v"]
484desc = "Start visual selection (motions extend, y yanks)"
485
486[workspace.vim_mode.actions.yank-section]
487keys = ["yah"]
488desc = "Yank current section"
489
490[workspace.vim_mode.actions.yank-paragraph]
491keys = ["yap"]
492desc = "Yank first visible paragraph"
493
494[workspace.vim_mode.actions.yank-code]
495keys = ["yac"]
496desc = "Yank first visible code block"
497
498[workspace.vim_mode.actions.yank-table]
499keys = ["yat"]
500desc = "Yank first visible table"
501
502[workspace.vim_mode.actions.yank-list]
503keys = ["yal"]
504desc = "Yank first visible list"
505
506[workspace.vim_mode.actions.confirm]
507keys = ["enter"]
508desc = "Open selected heading link and exit mode"
509
510[workspace.vim_mode.actions.cancel]
511keys = ["escape"]
512desc = "Cancel/exit mode"
513```
514
515Notes:
516- Vim Mode scrolls and navigates the active webpage inside the active pane only.
517- `gi` focuses the next visible editable page input, starting at the first when no input is active and wrapping after the last.
518- `]]` and `[[` move through visible `h1`–`h6` headings and use the current Dumber theme accent for the selected-heading outline.
519- `confirm` opens the first link inside the selected heading, or a link wrapping that heading, then exits Vim Mode. If the selected heading has no link, it only exits the mode.
520- The default `timeout_ms = 0` means Vim Mode stays active until you exit it or focus moves into an editable/browser UI context. `Ctrl+Y` can still activate it when a page input is already focused.
521- Arrow keys still use the browser engine's native page-navigation path while Vim Mode is active.
522- `f`, `F`, and `yf` label visible links; `yah`, `yap`, `yac`, `yat`, and `yal` copy a section, paragraph, code block, table, or list; `v` starts caret and visual selection. While a hint or visual interaction runs, the page owns every key except the activation shortcut (`activation_shortcut`, `Ctrl+Y` by default), which always leaves Vim Mode and ends the interaction, whatever the page does. These actions use the CEF engine path.
523- `Tab` and `Shift+Tab` keep focus traversal inside the page while a page input is focused.
524- Live input, heading, heading-link activation, and page-focus navigation currently use the CEF engine path; WebKit fallback supports the Vim scroll commands while equivalent semantic navigation is pending.
525- Other app-level shortcuts stay suspended until Vim Mode exits, except for the Vim Mode toggle itself.
526- `workspace.styling.vim_mode_color` colors the Vim frame; `workspace.styling.transition_duration` controls its pulse. The mode legend and toast use the workspace styling options below.
527- Scroll execution: CEF and WebKit both use the shared `BuildPageScrollByJS` resolver when the page is ready. Each step starts under the viewport center, walks up through ancestors that can move in the requested direction, and hands scrolling to the document when a nested container reaches its boundary. The application repeater owns held-key cadence; each engine executes one immediate scroll step per tick. Cross-origin frame contents remain best-effort. CEF may use native precision-wheel input only as a pre-frame fallback before the browser/main frame is ready.
528
529### Resize Mode
530
531| Key | Type | Default | Description |
532|-----|------|---------|-------------|
533| `workspace.resize_mode.activation_shortcut` | string | `"ctrl+n"` | Resize mode activation key |
534| `workspace.resize_mode.timeout_ms` | int | `3000` | Resize mode timeout (ms) |
535| `workspace.resize_mode.step_percent` | float | `5.0` | Split ratio step per keystroke (percent) |
536| `workspace.resize_mode.min_pane_percent` | float | `10.0` | Minimum pane size (percent) |
537| `workspace.resize_mode.actions` | map | See below | Action→keys mappings |
538
539**Default resize mode actions:**
540```toml
541[workspace.resize_mode.actions]
542resize-increase-left = ["h", "arrowleft"]
543resize-increase-down = ["j", "arrowdown"]
544resize-increase-up = ["k", "arrowup"]
545resize-increase-right = ["l", "arrowright"]
546resize-decrease-left = ["H"]
547resize-decrease-down = ["J"]
548resize-decrease-up = ["K"]
549resize-decrease-right = ["L"]
550resize-increase = ["+", "="]
551resize-decrease = ["-"]
552confirm = ["enter"]
553cancel = ["escape"]
554```
555
556Notes:
557- Directional actions (`resize-increase-*/resize-decrease-*`) move the split divider.
558- Smart actions (`resize-increase` / `resize-decrease`) grow/shrink the active pane (best-effort) by picking a direction automatically.
559- Timeout is refreshed on each resize keypress so you can keep adjusting without re-entering the mode.
560
561### Global Shortcuts
562
563Global shortcuts are configured under `workspace.shortcuts.actions` using the same `ActionBinding` structure as modal keybindings:
564
565| Action | Default Keys | Description |
566|--------|--------------|-------------|
567| `toggle-floating-pane` | `alt+f` | Toggle the workspace floating pane (persistent session) |
568| `close-pane` | `ctrl+w` | Close active pane (or release floating pane when floating is active) |
569| `next-tab` | `ctrl+tab` | Switch to next tab |
570| `previous-tab` | `ctrl+shift+tab` | Switch to previous tab |
571| `consume-or-expel-left` | `alt+[` | Consume into left sibling stack, or expel left if stacked |
572| `consume-or-expel-right` | `alt+]` | Consume into right sibling stack, or expel right if stacked |
573| `consume-or-expel-up` | `alt+{` | Consume into upper sibling stack, or expel up if stacked |
574| `consume-or-expel-down` | `alt+}` | Consume into lower sibling stack, or expel down if stacked |
575
576**Example:**
577```toml
578[workspace.shortcuts.actions.close-pane]
579 keys = ["ctrl+w"]
580 desc = "Close active pane"
581
582[workspace.shortcuts.actions.next-tab]
583 keys = ["ctrl+tab", "alt+l"]
584 desc = "Switch to next tab"
585
586[workspace.shortcuts.actions.toggle-floating-pane]
587 keys = ["alt+f"]
588 desc = "Toggle floating pane"
589```
590
591> **Tip:** Use `dumb://config` → Keybindings tab to edit shortcuts visually.
592>
593> **Note:** New tab creation uses modal tab mode (Ctrl+T then n/c). This follows the Zellij-style modal keyboard interface.
594>
595> **Note:** Consume-or-expel is experimental and may change behavior between releases.
596>
597> **Warning:** Some `Alt+<key>` shortcuts may conflict with WebKit defaults, website handlers, or your desktop environment.
598> If a binding does not fire, rebind it to a different key.
599
600### Floating Pane
601
602The floating workspace pane is a persistent overlay session: hiding it does not destroy its web content.
603
604- Default behavior includes exactly one floating shortcut: `Alt+F` (`toggle-floating-pane`).
605- URL shortcuts (for example `Alt+G`) are user-defined via `workspace.floating_pane.profiles` and are empty by default.
606- `Alt+F` toggles visibility only and preserves floating state.
607- `Ctrl+W` on an active floating pane closes and releases that floating session for a fresh next open.
608- Floating profile keybindings support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).
609
610| Key | Type | Default | Description |
611|-----|------|---------|-------------|
612| `workspace.floating_pane.width_pct` | float | `0.82` | Floating pane width as fraction of workspace width (`(0,1]`) |
613| `workspace.floating_pane.height_pct` | float | `0.72` | Floating pane height as fraction of workspace height (`(0,1]`) |
614| `workspace.floating_pane.profiles` | map | `{}` | Named URL profiles, each with `keys[]`, `url`, optional `desc` |
615
616**Example:**
617
618```toml
619[workspace.shortcuts.actions.toggle-floating-pane]
620keys = ["alt+f"]
621desc = "Toggle floating pane"
622
623[workspace.floating_pane]
624width_pct = 0.82
625height_pct = 0.72
626
627[workspace.floating_pane.profiles]
628# Empty by default. Add as many entries as needed.
629
630[workspace.floating_pane.profiles.google]
631keys = ["alt+g"]
632url = "https://google.com"
633desc = "Open floating pane on Google"
634
635[workspace.floating_pane.profiles.github]
636keys = ["alt+h"]
637url = "https://github.com"
638desc = "Open floating pane on GitHub"
639```
640
641### Browsing Context Behavior
642
643| Key | Type | Default | Valid Values | Description |
644|-----|------|---------|--------------|-------------|
645| `workspace.browsing_contexts.behavior` | string | `"split"` | `split`, `stacked`, `tabbed`, `windowed` | Placement mode for script-opened browsing contexts |
646| `workspace.browsing_contexts.placement` | string | `"right"` | `right`, `left`, `top`, `bottom` | Split direction when behavior is `split` |
647| `workspace.browsing_contexts.open_in_new_pane` | bool | `true` | - | Allow new browsing contexts to open in the workspace |
648| `workspace.browsing_contexts.follow_pane_context` | bool | `true` | - | Keep new browsing contexts aligned with the parent pane context |
649| `workspace.browsing_contexts.blank_target_behavior` | string | `"stacked"` | `split`, `stacked`, `tabbed` | Placement mode for `_blank` / new-page link contexts |
650| `workspace.browsing_contexts.enable_smart_detection` | bool | `true` | - | Use window properties to refine browsing-context classification |
651| `workspace.browsing_contexts.oauth_auto_close` | bool | `true` | - | Auto-close OAuth browsing contexts after success |
652
653### Workspace Styling
654
655| Key | Type | Default | Description |
656|-----|------|---------|-------------|
657| `workspace.styling.border_width` | int | `1` | Active pane border width (px) - overlay |
658| `workspace.styling.border_color` | string | `"@theme_selected_bg_color"` | Active pane border color |
659| `workspace.styling.mode_border_width` | int | `4` | Reserved; mode frame borders currently use fixed em widths (Vim is thinner) |
660| `workspace.styling.pane_mode_color` | string | `"#4A90E2"` | Pane mode color (blue) |
661| `workspace.styling.tab_mode_color` | string | `"#FFA500"` | Tab mode color (orange) - used for border and toaster |
662| `workspace.styling.session_mode_color` | string | `"#9B59B6"` | Session mode color (purple) - used for border and toaster |
663| `workspace.styling.vim_mode_color` | string | `"#F7768E"` | Vim mode border, pulse, toast, and legend color |
664| `workspace.styling.resize_mode_color` | string | `"#00D4AA"` | Resize mode color (teal) - used for border and toaster |
665| `workspace.styling.mode_indicator_toaster_enabled` | bool | `true` | Show the bottom-left mode toaster header while a mode is active. When `false` and `mode_legend` is not `off`, the header is still shown because the legend expands from it |
666| `workspace.styling.mode_legend` | string | `"delay"` | `always`, `delay`, or `off`; the legend expands from the bottom-left mode toaster in that mode's color. `off` keeps only the toaster header, without the expand/collapse chevron |
667| `workspace.styling.mode_legend_delay_ms` | int | `500` | Wait before showing the expanded legend when set to `delay` (>= 0) |
668| `workspace.styling.mode_legend_linger` | bool | `true` | Keep the legend visible, read-only, after its mode ends while the pointer is over it, until the next key press or click. |
669| `workspace.styling.mode_legend_animations` | bool | `true` | Animate the legend and its keycap feedback |
670| `workspace.styling.mode_legend_collapsed` | bool | `false` | Collapse the legend to the mode toaster header. Clicking the toaster toggles it, applies immediately in every window, and is saved here for all modes |
671| `workspace.styling.transition_duration` | int | `120` | Base Vim border pulse duration (ms); legend appearance is 120 ms |
672
673**Example:**
674
675```toml
676[workspace.styling]
677border_width = 1
678border_color = "@theme_selected_bg_color"
679mode_border_width = 4
680pane_mode_color = "#4A90E2" # Blue for pane mode
681tab_mode_color = "#FFA500" # Orange for tab mode
682session_mode_color = "#9B59B6" # Purple for session mode
683vim_mode_color = "#F7768E" # Vim mode
684resize_mode_color = "#00D4AA" # Teal for resize mode
685mode_indicator_toaster_enabled = true
686mode_legend = "delay"
687mode_legend_delay_ms = 500
688mode_legend_linger = true
689mode_legend_animations = true
690mode_legend_collapsed = false
691transition_duration = 120
692```
693
694## Session
695
696| Key | Type | Default | Description |
697|-----|------|---------|-------------|
698| `session.auto_restore` | bool | `false` | Automatically restore the last session on startup |
699| `session.snapshot_interval_ms` | int | `5000` | Minimum interval between snapshots in milliseconds |
700| `session.max_exited_sessions` | int | `50` | Maximum number of exited sessions to keep |
701| `session.max_exited_session_age_days` | int | `7` | Maximum age in days for exited sessions (auto-deleted on startup) |
702
703### Session Mode
704
705| Key | Type | Default | Description |
706|-----|------|---------|-------------|
707| `session.session_mode.activation_shortcut` | string | `"ctrl+o"` | Session mode activation key |
708| `session.session_mode.timeout_ms` | int | `3000` | Session mode timeout (ms) |
709| `session.session_mode.actions` | map | See below | Action to keys mappings |
710
711**Default session mode actions:**
712```toml
713[session.session_mode.actions]
714session-manager = ["s", "w"]
715confirm = ["enter"]
716cancel = ["escape"]
717```
718
719**Example:**
720```toml
721[session]
722auto_restore = false # Don't auto-restore on startup
723snapshot_interval_ms = 5000 # Save state every 5 seconds (debounced)
724max_exited_sessions = 50 # Keep last 50 exited sessions
725max_exited_session_age_days = 7 # Delete sessions older than 7 days on startup
726```
727
728## Media
729
730| Key | Type | Default | Valid Values | Description |
731|-----|------|---------|--------------|-------------|
732| `media.hardware_decoding` | string | `"auto"` | `auto`, `force`, `disable` | Hardware video decoding mode |
733| `media.prefer_av1` | bool | `false` | - | Prefer AV1 codec when available |
734| `media.show_diagnostics` | bool | `false` | - | Show media diagnostics warnings at startup |
735
736WebKit fallback GStreamer tuning is configured under `engine.webkit.force_vsync`, `engine.webkit.gl_rendering_mode`, and `engine.webkit.gstreamer_debug_level`.
737
738**Hardware decoding modes:**
739- `auto` (recommended): Hardware preferred with software fallback - fixes Twitch Error #4000
740- `force`: Hardware only - fails if unavailable
741- `disable`: Software only - higher CPU usage
742
743**GPU auto-detection:**
744Dumber automatically detects your GPU vendor (AMD/Intel/NVIDIA) and sets optimal VA-API driver settings:
745- **AMD**: Uses `radeonsi` driver
746- **Intel**: Uses `iHD` driver (modern, for Broadwell+)
747- **NVIDIA**: Uses `nvidia` driver with EGL platform
748
749**Example:**
750
751```toml
752[media]
753hardware_decoding = "auto" # HW preferred, SW fallback
754prefer_av1 = false # Let site choose codec
755show_diagnostics = false # Keep off for daily use
756```
757
758**WebKit fallback GStreamer diagnostics:**
759
760```toml
761[engine.webkit]
762gstreamer_debug_level = 3
763```
764
765Set `media.show_diagnostics = false` and `engine.webkit.gstreamer_debug_level = 0` to return to normal mode.
766
767**Diagnostics CLI:**
768
769```bash
770# Check GStreamer plugins and VA-API status
771dumber doctor --media
772```
773
774## Runtime
775
776| Key | Type | Default | Description |
777|-----|------|---------|-------------|
778| `engine.cef.cef_dir` | string | `""` | Optional CEF runtime directory containing `libcef.so` and resources |
779| `engine.webkit.prefix` | string | `""` | Optional WebKitGTK fallback runtime prefix (e.g., `/opt/webkitgtk`) |
780
781`engine.cef.cef_dir` overrides CEF runtime discovery. For the WebKitGTK fallback, `engine.webkit.prefix` prepends prefix-derived paths to:
782- `PKG_CONFIG_PATH` (for version checks)
783- `LD_LIBRARY_PATH` (for runtime library loading)
784- `GI_TYPELIB_PATH` (GObject introspection)
785- `XDG_DATA_DIRS` (schemas/resources)
786
787**Example:**
788
789```toml
790[engine.cef]
791cef_dir = "/opt/cef"
792
793[engine.webkit]
794prefix = "/opt/webkitgtk"
795```
796
797## Clipboard
798
799| Key | Type | Default | Description |
800|-----|------|---------|-------------|
801| `clipboard.auto_copy_on_selection` | bool | `true` | Automatically copy selected text to clipboard (zellij/tmux-style) |
802
803When enabled, selecting text in a web page immediately copies it to the clipboard with a brief toast notification. Does not apply to text selection in input fields or textareas.
804
805**Example:**
806
807```toml
808[clipboard]
809auto_copy_on_selection = true # Enabled by default
810```
811
812## Content Filtering
813
814| Key | Type | Default | Description |
815|-----|------|---------|-------------|
816| `content_filtering.enabled` | bool | `true` | Enable ad blocking |
817| `content_filtering.auto_update` | bool | `true` | Automatically update filters |
818
819Notes:
820- Filter data is downloaded from `bnema/ublock-webkit-filters` GitHub releases.
821- Domain whitelist is managed via the database (`content_whitelist` table), not the config file.
822
823## Update
824
825| Key | Type | Default | Description |
826|-----|------|---------|-------------|
827| `update.enable_on_startup` | bool | `true` | Check for updates when browser starts |
828| `update.auto_download` | bool | `false` | Automatically download updates in background |
829| `update.notify_on_new_settings` | bool | `true` | Show toast notification when new config settings are available |
830
831**Example:**
832
833```toml
834[update]
835enable_on_startup = true # Check for updates on startup
836auto_download = false # Don't auto-download (prompt instead)
837notify_on_new_settings = true # Show toast when config migration available
838```
839
840**CLI Commands:**
841
842```bash
843# Check config status and available migrations
844dumber config status
845
846# Add missing settings with default values
847dumber config migrate
848
849# Skip confirmation prompt
850dumber config migrate --yes
851```
852
853## Performance Profiles
854
855Performance profiles tune engine resource behavior. CEF is the default engine; the `engine.webkit.*` custom fields below apply only when using the WebKitGTK fallback.
856
857> **Note:** Performance settings are applied at browser startup. Changes require a restart to take effect.
858
859| Key | Type | Default | Valid Values | Description |
860|-----|------|---------|--------------|-------------|
861| `engine.profile` | string | `"default"` | `default`, `lite`, `balanced`, `max`, `custom` | Performance profile selection |
862| `engine.pool_prewarm_count` | int | `4` | `>= 0` | WebViews to pre-create at startup |
863| `engine.zoom_cache_size` | int | `256` | `>= 0` | Domain zoom levels to cache |
864
865### Profiles
866
867| Profile | Description | Use Case |
868|---------|-------------|----------|
869| `default` | No tuning, uses engine defaults | Compatibility baseline, troubleshooting |
870| `lite` | Reduced resource usage | Low-RAM systems (< 4GB), battery saving |
871| `balanced` | Moderate tuning | General use |
872| `max` | Maximum responsiveness | Heavy pages (GitHub PRs, complex SPAs), high-end systems |
873| `custom` | Manual control over all settings | Advanced users who want fine-grained control |
874
875**Example:**
876
877```toml
878[engine]
879profile = "default"
880pool_prewarm_count = 4
881zoom_cache_size = 256
882```
883
884### WebKit fallback custom profile settings
885
886When `engine.profile = "custom"` and `engine.type = "webkit"`, you can configure individual WebKit tuning options:
887
888| Key | Type | Default | Description |
889|-----|------|---------|-------------|
890| `engine.webkit.skia_cpu_painting_threads` | int | `0` | Skia CPU rendering threads (0=unset) |
891| `engine.webkit.skia_gpu_painting_threads` | int | `-1` | Skia GPU rendering threads (-1=unset, 0=disable) |
892| `engine.webkit.skia_enable_cpu_rendering` | bool | `false` | Force CPU rendering |
893| `engine.webkit.web_process_memory_limit_mb` | int | `0` | Web process memory limit in MB (0=unset) |
894| `engine.webkit.web_process_memory_poll_interval_sec` | float | `0` | Memory check interval (0=WebKit default: 30s) |
895| `engine.webkit.web_process_memory_conservative_threshold` | float | `0` | Conservative cleanup threshold (0=unset) |
896| `engine.webkit.web_process_memory_strict_threshold` | float | `0` | Strict cleanup threshold (0=unset) |
897| `engine.webkit.network_process_memory_limit_mb` | int | `0` | Network process memory limit in MB |
898| `engine.webkit.network_process_memory_poll_interval_sec` | float | `0` | Network memory check interval |
899| `engine.webkit.network_process_memory_conservative_threshold` | float | `0` | Network conservative threshold |
900| `engine.webkit.network_process_memory_strict_threshold` | float | `0` | Network strict threshold |
901
902**Custom WebKit fallback example:**
903
904```toml
905[engine]
906type = "webkit"
907profile = "custom"
908pool_prewarm_count = 6
909zoom_cache_size = 256
910
911[engine.webkit]
912skia_cpu_painting_threads = 4
913skia_gpu_painting_threads = 2
914web_process_memory_limit_mb = 1024
915web_process_memory_conservative_threshold = 0.4
916web_process_memory_strict_threshold = 0.6
917```
918
919> **Important:** Individual `engine.webkit.*` tuning fields are ignored unless `engine.profile = "custom"`.
920
921## Downloads
922
923| Key | Type | Default | Description |
924|-----|------|---------|-------------|
925| `downloads.path` | string | `""` | Download directory path (empty = `$XDG_DOWNLOAD_DIR` or `~/Downloads`) |
926
927Downloads are saved to the configured directory with toast notifications for download started, completed, and failed events.
928
929**Example:**
930```toml
931[downloads]
932path = "" # Use system default ($XDG_DOWNLOAD_DIR or ~/Downloads)
933
934# Or specify a custom directory:
935# path = "/home/user/my-downloads"
936```
937
938## Environment Variables
939
940All config values can be overridden via environment variables with the prefix `DUMBER_`:
941
942```bash
943# Database
944DUMBER_DATABASE_PATH=/custom/path/db.sqlite
945
946# Engine/rendering (examples)
947DUMBER_ENGINE_TYPE=cef
948# CEF defaults to the GPU-first Vulkan DMABUF stack.
949# Use EGL/OpenGL for driver compatibility:
950# DUMBER_ENGINE_CEF_RENDER_STACK=egl
951# WebKit fallback rendering overrides:
952# DUMBER_ENGINE_WEBKIT_DISABLE_DMABUF_RENDERER=true
953# DUMBER_ENGINE_WEBKIT_GSK_RENDERER=opengl
954DUMBER_DEFAULT_WEBPAGE_ZOOM=1.5
955
956# Logging
957DUMBER_LOG_LEVEL=debug
958DUMBER_LOG_FORMAT=json
959```