Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration

There is one TOML file. The complete default below is generated from the code, so it cannot drift from what the shell actually reads.

The config file

The file is $XDG_CONFIG_HOME/sottomano.toml, usually ~/.config/sottomano.toml. Every key is optional, so the file only needs what differs from the defaults. sottomano default-config prints the full default, and its output is a working starting point for the file.

The running shell watches the file and applies every saved change in place. Unknown keys are rejected. An invalid file raises a toast naming the problem, and the previous configuration stays in use.

With home-manager, services.sottomano.settings takes the same keys as Nix attributes, and the module writes them to that file verbatim.

Compositors

The same file works on niri, sway and Hyprland. The gamepad’s compositor_actions name compositor-neutral actions; on a compositor without an equivalent, such as toggle_overview on sway, pressing the button shows a toast saying so. The window styling group keeps its settings in the state database and writes them as rules for the running compositor, which its page explains how to include: niri, sway, Hyprland.

Full default

Every key with its default, as sottomano default-config prints it.

[theme.palette]
background = "#0b0e14"
text = "#e6e1cf"
primary = "#95e6cb"
success = "#aad94c"
warning = "#ffb454"
danger = "#f07178"
background_weak = "#202229"
background_strong = "#3e4b59"
secondary = "#bfbdb6"

[theme.font]
text_size = "14px"

[theme.spacing]
item_gap = "4px"
surface_inset = "10px"
group_gap = "14px"

[desktop_panels]
height = "24px"

[desktop_panels.slot_drag]
ghost_scale = 1.08
spring_load_delay = "600ms"
focus_indicator_duration = "160ms"

[group_menus]
alert_promotion_severity = "warning"

[widget_group_layout]
expansion_duration = "90ms"

[group_items]
slider_expansion_duration = "90ms"

[hover_cards]
copy_flash_duration = "700ms"

[hover_cards.loading]
debug_delay = "0s"

[hover_cards.loading.placeholder]
effect = "pulse"
period = "1s 400ms"

[hover_cards.loading.intro]
effect = "fade"
duration = "500ms"

[hover_cards.loading.replace]
effect = "decode"
duration = "350ms"
glyph_change = "55ms"

[overlay_surfaces]
surface_morph_duration = "160ms"
typing_idle_delay = "750ms"
typing_background_opacity = 0.3

[widget_groups.window]
audio_indicator = "shown"
progress = "shown"
count_badge = "shown"
nixpkgs_branch = "nixpkgs-unstable"
maximum_window_title_width = "200px"

[widget_groups.window.agents]
kitty = "enabled"
wezterm = "enabled"
pty_shells = "enabled"

[widget_groups.workspaces]
preview_scale = 0.19

[widget_groups.tray.items.Signal_status_icon_1]
icon = "shown"
left_click = "focus_app_window"
middle_click = "secondary_activate_item"
right_click = "ignore"
window_app_id = "signal"

[widget_groups.tray.items.spotify-client]
icon = "shown"
left_click = "focus_app_window"
middle_click = "secondary_activate_item"
right_click = "ignore"
window_app_id = "spotify"

[widget_groups.tray.items.steam]
icon = "shown"
left_click = "focus_app_window"
middle_click = "secondary_activate_item"
right_click = "ignore"
window_app_id = "steam"

[widget_groups.compute.temperature_sensors]

[widget_groups.memory_board.growth_trend]
trend_window = "2m"
warning_when_full_within = "5m"
critical_when_full_within = "1m"

[widget_groups.physical_drive.temperature_sensors]

[widget_groups.systemd_manager]
slow_job = "10s"
slow_boot_unit = "5s"
slow_shutdown_stop = "5s"
slower_boot_warning_percent = 25
slower_boot_critical_percent = 60

[widget_groups.claude_usage]
monthly_budget_usd = 200

[widget_groups.copilot_usage]
monthly_budget_usd = 40

[widget_groups.openrouter_credit]
monthly_budget_usd = 20
daily_warning_usd = 5
max_blended_usd_per_million = 4.0

[widget_groups.session_focus]
idle_detection_delay = "30s"
shortest_counted_focus = "1s"
idle_after = "3m"
session_warning = "45m"
session_critical = "1h 30m"
warning_snooze = "15m"
switches_per_hour_warning = 100
switches_per_hour_critical = 300
switch_heat_span = "30m"
switch_heat_warning = 20
switch_heat_critical = 32

[widget_groups.session_focus.app_daily_budget_minutes]

[widget_groups.session_focus.project_daily_budget_minutes]

[widget_groups.weather.rain_alerts]
lead_time = "15m"
minimum_millimeters_per_hour = 0.5
heavy_millimeters_per_hour = 4.0

[widget_groups.reachability]
latency_target = "1.1.1.1"
metered_cycle_start_day = 1

[widget_groups.gamepad.navigation]
stick = "left"
up = "focus_workspace_up"
down = "focus_workspace_down"
left = "focus_left"
right = "focus_right"

[widget_groups.gamepad.pointer]
stick = "right"
pixels_per_second = 1600
left_button = "right_trigger"
right_button = "right_stick"

[widget_groups.gamepad.typed_keys]
south = "Return"
east = "BackSpace"
north = "shift+Tab"
west = "Escape"
dpad_up = "Up"
dpad_down = "Down"
dpad_left = "Left"
dpad_right = "Right"
back = "Tab"

[widget_groups.gamepad.compositor_actions]
left_shoulder = "move_left"
right_shoulder = "move_right"
start = "maximize"
guide = "toggle_overview"
left_stick = "focus_previous_window"

[widget_groups.gamepad.commands]

[widget_groups.gamepad.push_to_talk]
button = "left_trigger"

[widget_groups.caldav]
server_url = ""
user = ""
upcoming_days = 7
change_poll_interval = "1m"
push_safety_recheck_interval = "15m"
subscription_download_interval = "15m"

[widget_groups.media.lyrics]
lrclib_lookup = "enabled"

[notification_toasts.placement]
fallback_corner = "bottom-right"

[notification_toasts.lifetime]
low_severity_timeout = "8s"
normal_severity_timeout = "12s"
critical_severity_timeout = "0s"

[notification_toasts.card]
appear = "90ms"
dismiss_fade = "140ms"
expire_fade = "220ms"
motion = "160ms"

[calendar]
other_time_zones = ["Asia/Shanghai"]

[calendar.working_hours]
start = "09:00:00"
end = "17:00:00"

[launched_programs]
terminal_command_prefix = ["kitty", "-e"]
color_temperature_service = "screen-temperature.service"

Tray clicks

A left click on a tray icon brings the app’s window to the front. The window is found through the process that owns the tray icon on D-Bus, or through window_app_id for apps whose window belongs to another process. When no single window matches, the click activates the item instead. The shell first gets an XDG activation token from the compositor and passes it to the app, so the app may raise the window it opens. Tray menu clicks pass a token the same way. The hover hint shows which of the two a click will do.

X11 apps run through xwayland-satellite, so the compositor reports the satellite as the owner of their windows. They need window_app_id, as the default rule for Steam has. Apps whose tray item cannot take an activation token, such as libappindicator items and X11 apps, may still fail to come to the front from their own menu items. That limit is in the app.

To keep an app’s own activate behaviour, set left_click = "activate_item" for its item, keyed by the item’s StatusNotifierItem id.

Application menu

The application menu is off by default; the Behaviour menu has the switch. When it is on, the top panel gets an Application menu with the focused window’s menu bar: each top-level menu is a group, its entries are rows, and entries of submenus appear as Parent › Entry. Single entries and whole menus can be pinned to the panel like any other group.

Qt apps export their menu bar over D-Bus only when a global menu registrar is running when they start, so while the switch is on the shell claims the com.canonical.AppMenu.Registrar name. Qt apps started after that hide the menu bar inside their window; apps started before keep theirs until they are restarted. GTK apps that publish a menu bar show it in both places.

X11 apps are matched to their exact window: through the registrar, through the menu properties KDE apps set on their window, or through the ones GTK apps and LibreOffice set. Native Wayland apps are matched through the process that owns the window, so when one process has several menu bars, the most recent one is shown and its hover card says so. Browsers and Electron apps running natively on Wayland export no menu, and apps built on libadwaita have no menu bar.

Secrets

A few widgets need an API key or password. Each one is optional, and the widgets that need a missing one stay hidden. sottomano secrets lists the secret files in ~/.config/sottomano/, what each one enables and whether it is present.

With home-manager, services.sottomano.secretFiles.<name> symlinks each one from wherever the secret actually lives, so nothing lands in the Nix store.