Skip to content

Config Reference

Lshika edited this page Aug 17, 2026 · 18 revisions

Config Reference

Your config lives at ~/.config/tuicc/config.toml. If it's missing, tuicc creates it by copying the packaged default the first time it runs — you always have a real, editable file with sensible defaults, never a hidden in-memory fallback.

If you add or edit sections here and tuicc throws a KeyError on startup, your existing config is probably out of date with a newer packaged default (a new section was added since your config was generated). Delete ~/.config/tuicc/config.toml and run tuicc again to regenerate it with the current defaults, then reapply your customizations. There's no automatic migration yet — this is a known, not-yet-solved gap.


[layout]

[layout]
preset = 1
Key Type Description
preset integer Which layout preset to use. Live-switchable — change this any time, not just on first run.

Where a preset actually lives: ~/.config/tuicc/presets/<N>.toml is the real, editable file tuicc reads — copied there from the packaged template (src/tuicc/presets/<N>.toml) the first time that number is requested, and never touched again after that unless you edit it yourself (or tuicc's own resize mode saves a new one — see below). If you edit a preset and want to reset it back to the packaged version, delete ~/.config/tuicc/presets/<N>.toml and it'll be recopied next time that number is used.

Preset box fields

A preset is a list of [[box]] entries, each needing a name plus x, y, w, h — all four required, all plain ratios (0.0–1.0) of the terminal's width (x, w) or height (y, h):

[[box]]
name = "sidebar"
x = 0.0
y = 0.0
w = 0.26
h = 0.6

Boxes are completely independent — there's no right_of/below/above/bottom/cols/rows/fill_to system anymore (an earlier version of tuicc had one; it was removed once interactive resize mode made "fix it when you see it" cheap enough that the auto-coordination it existed for stopped earning its cost — see Architecture for the full reasoning). Changing one box's position or size never moves or resizes another. If a box looks wrong on a terminal size very different from the one you set it up on, that's expected, not a bug — fix it with resize mode (below) rather than hand-computing ratios.

Editing a layout from inside tuicc (resize mode)

You don't have to hand-edit preset TOML at all — press resize (F2 by default) to open resize mode. It's a two-level session, not a single-shot mode:

  • Browsing (right after F2): nothing's being edited yet — Tab/Shift+Tab/arrows just pick which module resize will act on next, same as normal navigation. confirm (Enter) on the active module drops into editing that one box.
  • Editing one box: arrow keys change its size (w/h) by default; press move_toggle (m) to switch to changing its position (x/y) instead, and again to switch back. confirm here commits the change (in memory only, nothing written to disk yet) and drops back to browsing — not out of the session — so you can edit as many other modules as you like before saving anything. Escape here reverts just this box to how it was when you entered editing on it (or removes it, if it's a box you just spawned with no "before" state) and also drops back to browsing.
  • delete_box (Delete) asks y/n before removing the current box from the layout entirely — works at either level.
  • Escape while browsing (not editing) ends the whole session and returns to normal navigation. So does F3/F4/F5 below, from either level.
  • spawn_box (F6), save_layout (F3), cycle_preset (F4), new_preset (F5), and help (F1, described below) all work from either level — pressing one while a box is being edited first commits that in-progress change (same as confirm would), then does its own thing.

spawn_box (F6) lists every module not currently placed in your layout (numbered), and adding one via a digit key spawns it centered on screen, dropped straight into editing so you can immediately position it.

save_layout (F3) writes your entire in-memory layout back to the active preset file, overwriting it in place (save_layout_to_preset() in config.py) — no new preset number is minted. F3 (like F4/F5 below) always ends the whole resize session, not just the current box's edit.

cycle_preset (F4) switches through every preset number that actually exists (packaged or yours), live — each press changes what's on screen and updates [layout] preset in config.toml to match (set_active_preset(): it patches just that one line by hand, not a full rewrite, so the rest of your hand-edited config.toml — comments, formatting, everything — survives untouched).

new_preset (F5) forks your current in-memory layout into a brand-new preset number instead of overwriting the active one (save_new_preset() in config.py — the function save_layout/F3 above deliberately doesn't call), then switches to it — the way to start a fresh preset from a layout you like without hand-editing files.

help (F1) isn't part of resize mode itself, but is worth knowing about here too — it opens an in-app reference covering all of this (plus your current keybinds and a live [theme] color editor) without needing this wiki open. See Keybindings: Help menu for the full page-by-page rundown.


[navigation]

[navigation]
tab_order = "columns_first"
vim_mode = false
Key Type Description
tab_order "columns_first" | "rows_first" Order Tab cycles through items within a module. columns_first: top-to-bottom within a column, then next column. rows_first: left-to-right within a row, then next row.
vim_mode bool If true, ambient typing no longer opens the launcher — you have to press the insert key (see [navigation.keys] below) first. This exists so vim-style bindings like h/j/k/l don't collide with typing search text.

[wm]

[wm]
provider = "sway"
total_workspaces = 10
self_app_id = ""
return_to_origin = false
fullscreen_only = true
Key Type Description
provider string Which WM provider to use. Built in: sway, i3. See Writing a WM Provider to add your own — once registered in providers/registry.py, its name becomes valid here.
total_workspaces integer How many workspace slots the sidebar always shows, regardless of how many are actually in use. Empty slots render thin; occupied ones grow to list every window.
self_app_id string Empty by default. If set (e.g. "tuicc_scratch", matching how you launch tuicc — see Summoning tuicc), sway/i3 mark and dismiss tuicc's own window by WM criteria instead of "whatever's currently focused" — removes a focus-timing race on the fallback path (empty string).
return_to_origin bool Off by default. If true, pressing top-level Escape also focuses back to whatever window/region had focus right before tuicc's own window was last focused — on top of the normal dismiss.
fullscreen_only bool Packaged default true (a config missing this key entirely falls back to false instead — deliberate, so upgrading an existing config doesn't silently change its behavior; only a fresh install gets true). When true, tuicc re-asserts fullscreen on itself right after a launcher spawn or session restore resolves — sway/i3 both drop a container back to plain floating the instant any new window is mapped anywhere in your session, even briefly on tuicc's own workspace before tuicc gets a chance to move it elsewhere (see Architecture for the underlying mechanism). Only meaningful if your WM config's for_window rule (or the toggle scripts in contrib/, which read this same value at runtime) actually uses fullscreen enable in the first place — set this false and drop fullscreen enable from your for_window rule if you'd rather run tuicc as a plain floating window.

Not a config.toml key, but a required setup step: tuicc needs your WM to launch it floating, scratchpadded, and bound to a toggle key — and, ideally, tagged with a stable self_app_id so it can mark and dismiss its own window unambiguously. Quick install (install.sh) sets all of this up for you, fullscreen_only included. Doing it by hand instead? See Summoning tuicc for the full per-WM snippets (sway/scroll, i3, Hyprland, niri) and contrib/sway/tuicc_toggle.py/contrib/i3/tuicc_toggle.py for ready-made single-key summon/dismiss/focus toggle scripts.


[theme]

[theme]
background = "inherit"
border = "white"
border_selected = "white"
text = "white"
accent = "cyan"
selected = "blue"
warning = "yellow"
urgent = "red"

Every role accepts one of four formats:

Format Example Notes
"inherit" "inherit" Use the terminal's own color for this role — curses's default color (-1). Typically used for background.
Named color "cyan" One of: black, red, green, yellow, blue, magenta, cyan, white.
Hex string "#7dd3fc" Approximated to the nearest color in curses's 256-color cube — not exact true-color, since curses doesn't support that directly.
RGB list [125, 211, 252] Same approximation as hex, different input format. "#7dd3fc" and [125, 211, 252] resolve to the identical color.

Note: a named color and its hex/RGB equivalent don't necessarily resolve to the same internal color number — named colors map directly to curses's base 8, while hex/RGB always goes through the 256-color cube approximation. "red" and "#ff0000" are different numbers internally, even though they look the same.

You don't have to hand-edit this section either — the Help menu's Colors page (help, F1, then 3) edits and previews all 8 roles live from inside tuicc, and writes the result back here for you. It only accepts a named color, #hex, or "inherit" through that UI though — an [R, G, B] list still needs hand-editing.

That same page can also apply a whole theme at once instead of editing role-by-role: F4 cycles through 9 built-in named schemes (Dracula, Nord, Solarized Dark, Gruvbox, One Dark, Rose Pine, Catppuccin Mocha, Tokyo Night, plus tuicc's own Default) and any presets you've saved yourself, F5 saves your current 8 values as a new one under ~/.config/tuicc/theme_presets/<N>.toml. See Keybindings: Help menu for the full behavior, including cycle_theme_preset (F7), a standalone key that cycles the same list from anywhere, not just this page.

Roles

Role Used for
background Overall background — genuinely paints the whole screen (stdscr.bkgd(), plus every other role's own curses pair carries this same color on its background component so text blends in rather than showing a mismatched box around itself). "inherit" (curses's default, -1) leaves your terminal's own background alone, which is what tuicc's own Default scheme uses; every other built-in scheme sets a real, deliberately darkened hex value instead (see theme_presets.py's own module docstring for why darkened rather than desaturated).
border Default box outline color for modules and items.
border_selected Outline color for the currently active module (the one Shift+Tab last landed on) — see Keybindings.
text Default, unselected text.
accent Highlight color — currently used for floating window boxes in the preview.
selected Text/border color for the currently selected item within a module.
warning Reserved for non-critical alerts. Not yet used by any built-in module.
urgent Critical/destructive emphasis — used for confirm prompts on destructive actions (power menu, quick actions) and for confirm = true entries in the list before they're even selected.

[navigation.keys]

[navigation.keys]
left = "Left"
right = "Right"
up = "Up"
down = "Down"
tab = "Tab"
previous = "Shift+Tab"
confirm = "Enter"
confirm_yes = "y"
confirm_no = "n"
insert = "i"
vim_left = "h"
vim_right = "l"
vim_up = "k"
vim_down = "j"
help = "F1"
resize = "F2"
save_layout = "F3"
cycle_preset = "F4"
new_preset = "F5"
spawn_box = "F6"
cycle_theme_preset = "F7"
move_toggle = "m"
delete_box = "Delete"
scan = "s"
wifi_forget = "f"
wifi_connect_hidden = "h"
wifi_power_toggle = "p"
bt_power_toggle = "p"
bt_pairable_toggle = "a"
Key Description
tab Move to the next item, rolling into the next module's first item once you run past the last item in the current one.
previous Tab's exact mirror — previous item, rolling into the previous module's last item.
left, right Jump straight to the previous/next module's first item — a coarser, distinct action from tab/previous, skipping past whatever's left in the current module.
up, down Pure duplicates of previous/tabup = previous item, down = next item, same rolling behavior.
vim_left, vim_right, vim_up, vim_down Duplicate left/right/up/down, but only take effect when [navigation].vim_mode = true — inert otherwise, so h/j/k/l don't steal 4 letters from the launcher's ambient typing for everyone.
confirm Activate the selected item — switch workspace, focus a window, connect/disconnect wifi or bluetooth, launch an app, or run a power-menu/quick action (prompting first if the action has confirm = true). Dismisses tuicc afterward (hides it, doesn't end the process) except for the launcher and connectivity, which stay open.
confirm_yes, confirm_no Answer a Y/N confirmation dialog. The on-screen hint always reflects whatever you bind here — rebind these and the dialog's text updates to match, no separate step needed.
insert Only used when [navigation].vim_mode = true — press this to start typing into the launcher, since ambient typing is disabled in vim mode.
help Opens the in-app help menu — keybinds/FAQ, a resize-mode reference, and a live [theme] color editor/preset cycler. See Keybindings: Help menu.
spawn_box, resize, save_layout, cycle_preset, new_preset, move_toggle, delete_box Interactive layout editing (resize mode) — see Config Reference: Editing a layout from inside tuicc above and Keybindings for the full behavior. cycle_preset/new_preset also do double duty on the Colors page (see [theme] above). move_toggle only does anything while resize mode is already active and editing a box; delete_box works at either resize-mode level; the rest also work from normal navigation.
cycle_theme_preset Cycles through every built-in + user-saved theme preset, from anywhere — not gated to the Colors page or a resize session. See [theme] above and Keybindings: Help menu.
scan, wifi_forget, wifi_connect_hidden, wifi_power_toggle, bt_power_toggle, bt_pairable_toggle Only meaningful while browsing WiFi/Bluetooth (Enter on the WiFi or Bluetooth header claims the whole box — see connectivity.py's own "level-2 browsing" design). scan rescans/rediscovers on whichever section you're browsing (WiFi or Bluetooth). wifi_forget/wifi_connect_hidden/wifi_power_toggle are WiFi-only: wifi_forget forgets the selected known network (asks Y/N first); wifi_connect_hidden types in and connects to a network that doesn't broadcast its SSID; wifi_power_toggle turns the WiFi radio itself on/off. bt_power_toggle/bt_pairable_toggle are Bluetooth's own equivalents — adapter power and whether it accepts new pairing requests. wifi_power_toggle and bt_power_toggle deliberately share the same key (p) — the Connectivity box's own header legend ([P]WR/[S]CAN/P[A]IRABLE) needs one shared letter to read correctly for both sections at a glance. A "Powering on…/off…" line shows in the preview panel while a power toggle is in flight, and the browsing hint narrows to just that key while the radio is off.

Accepted values: named specials (Left, Right, Up, Down, Tab, Shift+Tab, Enter, Escape, Space, Delete, F1F12), any single character (the vim_left/vim_right/vim_up/vim_down keys above are ordinary single-character bindings, "h"/"l"/"k"/"j" by default — nothing special about them beyond that), or Ctrl+<letter> (e.g. "Ctrl+L"). Uppercase letters work as an implicit Shift — curses reports "A" and "a" as different key codes on their own. Ctrl+<letter> combos work reliably (a plain ASCII control code); general Shift+<key> combos other than Shift+Tab aren't attempted, since curses can't report them reliably across terminals. F-keys are their own separate namespace — deliberately used for resize mode's and the help menu's entry points instead of Ctrl+<letter> (already spoken for by power-menu-style global shortcuts) or a bare character (already spoken for by the launcher's "start typing from anywhere" fallback).

Ctrl+C is still the only real quit, and that's deliberate now, not a gap: tuicc is a persistent process meant to be summoned and hidden (dismissed), not relaunched every time — confirm/Escape dismiss it (hide it via the WM, process keeps running), and only Ctrl+C actually ends it. See Keybindings and Architecture: Process lifecycle for the full model.


[[quick_actions.action]]

[[quick_actions.action]]
label = "Lock"
icon = ""
command = "swaylock"
confirm = false
shell_true = false

[[quick_actions.action]]
label = "Shutdown"
icon = ""
command = "systemctl poweroff"
confirm = true
shell_true = false

Add as many [[quick_actions.action]] blocks as you want — each one is a separate entry in the quick-actions module. Not shown in the default layout preset — the module exists and works, but is reserved for something more open-ended later. Editing this section won't visibly change anything unless your preset actually places quick_actions somewhere.

Key Type Required Description
label string yes Shown in the UI.
command string yes Command run (via subprocess.Popen) when you select the action and press confirm.
icon string no (defaults to "") Shown before the label, if set.
confirm bool no (defaults to false) If true, pressing confirm shows a Y/N prompt in place of the action list instead of running the command immediately. Use this for anything destructive.
shell_true bool no (defaults to false) If false (the default), command runs as plain arguments — no shell involved, so nothing in the string is ever interpreted as shell syntax. Set to true only if the command genuinely needs shell syntax (pipes, ;, &&, $VARS) — see SECURITY for why this is opt-in rather than the default.
exit_after bool no (defaults to true) If true (the default), running the action dismisses tuicc (hides it via the WM) afterward. Set to false to keep tuicc open — useful for actions you might fire off several times in a row.

tuicc doesn't wait for or report on the command's success/failure; it launches it and then dismisses (or doesn't, per exit_after) immediately.


[[power_menu.action]]

[[power_menu.action]]
label = "Lock"
shortcut = "Ctrl+L"
icon = ""
command = "swaylock"
confirm = false
shell_true = false

[[power_menu.action]]
label = "Shutdown"
shortcut = "Ctrl+P"
icon = ""
command = "systemctl poweroff"
confirm = true
confirm_text = "Shut down now?"
shell_true = false

The module actually shown in the default layout — a vertical list, not capped at any fixed number of entries (the old 4-slot grid design was replaced with a plain list, so add as many as you like, limited only by how tall the box is). Same base fields as quick_actions.action, plus two more:

On i3: the swaylock/swaymsg exit commands shown above are sway defaults. Quick install detects i3 and swaps them for i3lock/i3-msg exit automatically; a manual git-clone setup needs the Lock/Logout command fields edited by hand (see the comment above [[power_menu.action]] in config.toml).

Key Type Required Description
label string yes Shown in the UI, and used in the confirm question if confirm_text isn't set.
command string yes Same as quick_actions.action.
icon string no Currently unused by the list-style rendering — kept for a possible future icon mode.
confirm bool no (defaults to false) Same as quick_actions.action.
shell_true bool no (defaults to false) Same as quick_actions.action — see that section above for the full explanation.
confirm_text string no A custom confirmation question (e.g. "Reboot now?"), shown above the Y/N prompt. Only meaningful when confirm = true — otherwise never shown. If omitted (with confirm = true), just the plain Y/N prompt shows, no question line.
shortcut string no A key (same format as [navigation.keys], typically "Ctrl+<letter>") that runs this action from anywhere in the running app, not just when this entry happens to be selected — and shows automatically as a [^X]-style prefix next to the label. Omit this entirely if you don't want a shortcut for that action; it's never forced on you.

Collision checking: every shortcut across every action, plus every [navigation.keys] binding, is checked against every other one at startup. Two things bound to the same key raise a KeyError naming both, rather than one silently winning — see Keybindings for more on how this works.


[rwb]

RWB — "real world box" — is what used to be a standalone clock module before it grew a date line and (opt-in, see [weather] below) a compact weather readout. Placed in the default layout preset, bottom-left by default.

[rwb]
time_format = "%H:%M:%S"
date_format = "%a %d.%m."
Key Type Description
time_format string strftime-style format string for the time.
date_format string strftime-style format string for the date.

The box itself stays deliberately compact — one extra line for weather, not a multi-line dump — with the full detail (today/tomorrow, wherever [weather] gets you) shown in the preview panel instead, when this box is selected.


[weather]

Opt-in — the whole section is commented out by default, same pattern as [[control.toggle]]'s shipped examples below. tuicc never picks a location source for you: exactly one of the three below must resolve once you uncomment this, or tuicc fails loudly at startup instead of silently guessing. See CLAUDE/NOTES/design-decisions.md#weather-location-sources in the main repo for the full reasoning.

[weather]
# lat = 50.0755
# lon = 14.4378
# name = "Praha"
# code = "PRG"
# geoclue = false
# ip_approx = false
Key Type Description
lat, lon float Most explicit location source — plain coordinates.
geoclue bool Ask GeoClue2 (D-Bus, WiFi-based precise positioning — the same mechanism Windows/macOS use) for your location. Needs geoclue2 installed and configured on your system — see CLAUDE/NOTES/known-limitations.md in the main repo.
ip_approx bool Rough location via your public IP address (ip-api.com — free, no key needed). Can be off by a city or more, especially on mobile/VPN/corporate networks.
name string Optional — overrides whatever display name the resolved source picks.
code string Optional — a short badge shown inline in the compact box line (e.g. "PRG ☀️ 18°C"). Purely cosmetic, not a real IATA/airport code lookup — tuicc doesn't derive or verify one, it just prints whatever you put here.

Exactly one of lat+lon, geoclue = true, or ip_approx = true must be set — never a silent auto-pick between them if you happen to set more than one, or none at all once the section itself is uncommented.


[title_condense]

[title_condense]
terminal_apps = ["kitty", "alacritty", "foot", "wezterm", "st", "urxvt", "xterm", "ghostty"]
browser_apps = ["librewolf", "firefox", "chromium", "chrome", "google-chrome"]
browser_title_names = ["librewolf", "mozilla firefox", "firefox", "chromium"]

Controls how the sidebar condenses a window's title down to just the useful part, keyed by app ID (lowercase):

Key Type Description
terminal_apps list of strings App IDs shown with their full window title — usually the running command or working directory, which is genuinely useful for a terminal.
browser_apps list of strings App IDs shown with just the site/page name, not the browser's own name repeated in every title.
browser_title_names list of strings Words filtered out of a browser title when picking the site name (e.g. so "GitHub — Mozilla Firefox" shows "GitHub", not "Mozilla Firefox").

Anything not in either list falls back to a generic heuristic: the first segment of the title, or nothing if that segment just repeats the app's own name.


[network]

[network]
wifi_backend = "iwd"
bluetooth_backend = "bluez"
Key Type Description
wifi_backend string Which wifi backend to use. Built in: iwd.
bluetooth_backend string Which bluetooth backend to use. Built in: bluez.

iwd is talked to directly over D-Bus (not by parsing iwctl's text output — its signal-strength display uses ANSI color to encode strength, not something safely text-parseable). bluez is driven via bluetoothctl CLI text, which doesn't have that problem. Connection requests run on a background thread (StatusWorker, see Architecture) — the render loop only ever reads cached state, never blocks waiting for a connect/disconnect to finish.


[audio]

[audio]
audio_backend = "wpctl"
Key Type Description
audio_backend string Which audio backend to use. Built in: wpctl (WirePlumber's own CLI — the right pick for basically every current sway/i3 setup, since PipeWire is the norm), pactl (plain PulseAudio fallback).

Only used by the media module's output-device switching — "music plays, route it to headphones" reuses this the same way set_default_sink is one flow, not the media module's own separate concern. Read via .get()-with-default ("wpctl"), not direct indexing the way [network]'s own backend fields are above — this section is newer (R5) than [network], and an existing config.toml from before it existed must not hard-crash on load just because the whole section is missing.

[media]

[media]
visible_slots = 3
Key Type Description
visible_slots int How many rows the Now Playing list and the Output list each show at once before scrolling kicks in (a fixed-slot-plus-scroll mechanic — unfilled slots show as [empty - player N]/[empty - output N], more than fit scrolls, reachable via Tab/Shift+Tab's own "peek" items). Defaults to 3 when this key (or the whole [media] table) is absent. [sysmon]'s own visible_slots below is the identical mechanic for the System module's window list — each configured independently, not one shared global. Found live, asked for directly, same session as [[sysmon.block]] below: "Počet viditelných řádků, visible slots a to same i pro media" (the visible-row count, and the same for media too).

Otherwise the module works automatically off whatever's running: any MPRIS-capable player (browsers, Spotify, mpv with the mpv-mpris plugin, ...) shows up in "Now Playing" the moment it starts, no per-app registration needed. The only other thing it reads from config is [audio] audio_backend above, for output switching.

Optional: the cava audio visualizer. If the cava binary is on your PATH, a small live frequency-bar strip renders next to the Output list. If it isn't, the media module just... doesn't show one — no error, no warning, nothing to configure. This is a deliberate product decision, not a gap: a missing optional visual extra has zero functional impact (unlike a real backend actually not working), so it doesn't get the same "surface it loudly" treatment [[control.toggle]] errors below do. install.sh prints a one-line note if cava isn't found, but never installs it for you — see the main README's "Optional external tools" section.


[[control.toggle]]

The control module: arbitrary user-defined toggles and cycles — night light, airplane mode, power profiles, do-not-disturb, mic mute, whatever on/off (or multi-state) shell command your own setup already has. Zero code needed per toggle; everything is status_command/command pairs you write yourself, same trust model as [[power_menu.action]]'s command.

[[control.toggle]]
label = "Night Light"
shell_true = false

  [[control.toggle.state]]
  name = "on"
  status_command = "pgrep -f gammastep"
  command = "gammastep -O 4500"

  [[control.toggle.state]]
  name = "off"
  command = "pkill -f gammastep"

A binary toggle and a multi-way cycle are the same mechanism, not two separate features. Every [[control.toggle]] needs label, shell_true, and 2 or more [[control.toggle.state]] blocks — Enter always advances to the next state, wrapping back to the first after the last. A 2-state block (above) IS a plain on/off toggle; add a third [[control.toggle.state]] and it becomes a 3-way cycle instead, no different contract, no separate config shape:

[[control.toggle]]
label = "Performance Mode"
shell_true = true

  [[control.toggle.state]]
  name = "power-saver"
  status_command = "[ \"$(powerprofilesctl get)\" = power-saver ]"
  command = "powerprofilesctl set power-saver"
  color = "blue"

  [[control.toggle.state]]
  name = "balanced"
  status_command = "[ \"$(powerprofilesctl get)\" = balanced ]"
  command = "powerprofilesctl set balanced"
  color = "white"

  [[control.toggle.state]]
  name = "performance"
  command = "powerprofilesctl set performance"
  color = "red"
Key Type Required Description
label string yes Shown in the UI, once per toggle (not per state).
shell_true bool no (defaults to false) Same meaning as [[power_menu.action]]'s own shell_truefalse (default) runs status_command/command as plain arguments, no shell involved; true only if either genuinely needs shell syntax (pipes, ;, &&, $VARS, [ ... ] tests) — see SECURITY for why this is opt-in. Applies to every state's commands under this toggle, not settable per-state.

Each [[control.toggle.state]] block:

Key Type Required Description
name string yes Internal state name — shown as [name] text next to the dot, but only for a 3+-state toggle (see "Dot rendering" below).
status_command string on every state except the last Exit code 0 means "the toggle is currently in THIS state." Checked in declaration order, first match wins. The last state may omit it entirely — if none of the earlier status_commands matched, the current state must be whichever one is left, by elimination, not a guess.
command string yes Run when Enter advances the toggle into this state (i.e. this is the target state's own activation command, not a repeat of whichever state you're leaving).
color string no Same format as [theme] colors ("inherit" / named / "#hex" / [R,G,B]). Omit it to use the module's own default dot color for that state instead.

Dot rendering: a 2-state toggle shows no [state] text at all — just a dot, filled for whichever state is declared first in the file, outline for the other, the same convention connectivity's connected/disconnected dot already uses. Declare your "on"/active state first if you want the filled dot to mean what you'd expect (every built-in example does). A 3+-state cycle keeps the [name] text next to the dot, since a plain filled/outline dot can't distinguish which of 3+ states you're in on its own — only per-state color can (see Performance Mode above, where each state gets its own color instead of relying on fill/outline).

No-silent-failure applies to your own commands too. If a state's command fails fast (exits within a short window — not a genuine long-runner like sleep infinity for an idle-inhibit toggle), the real captured error (stdout+stderr) shows in the UI instead of the toggle just silently staying in its old state with no explanation — see Architecture: StatusWorker for the general mechanism (get_action_error), found live specifically while building this module (a misconfigured gammastep command with no visible feedback at all was the original motivating case).

config.toml's packaged default ships six commented-out examples (Airplane Mode, Night Light, Mic Mute, Idle Inhibit, Do Not Disturb, Performance Mode) — broadly-applicable picks, not a "recommended defaults" list; several assume tools (mako, gammastep, powerprofilesctl) you may not have. Uncomment what applies to you, adapt the rest to your own tools. control itself is placed in the default layout preset already — every uncommented toggle just shows up in it, no [[box]] entry needed unless you're building your own preset from scratch (see Layout above).


[sysmon]

The System module: a scrollable per-window CPU/RAM list on top (CLOSE/KILL/NICE actions), and a compact stats grid in the middle — CPU/RAM/DISK/LOAD/CPUTEMP/HOT/SWAP — where every value shown, its position, and its warning/urgent coloring is a real config value, not something the module assumes for you.

[sysmon]
visible_slots = 3
Key Type Description
visible_slots int How many window rows show at once before the list starts scrolling — identical mechanic to [media]'s own visible_slots above, configured independently. Defaults to 3.

[[sysmon.block]]

The stats grid's own per-metric configuration, one entry per block:

[[sysmon.block]]
metric = "cpu"
column = 1
row = 1
warning = 70
urgent = 90

[[sysmon.block]]
metric = "cputemp"
column = 1
row = 2
warning = 75
urgent = 90

[[sysmon.block]]
metric = "hot"
column = 1
row = 3
warning = 75
urgent = 90

[[sysmon.block]]
metric = "ram"
column = 2
row = 1
warning = 75
urgent = 90

[[sysmon.block]]
metric = "disk"
column = 2
row = 2
warning = 80
urgent = 95

[[sysmon.block]]
metric = "load"
column = 3
row = 1

[[sysmon.block]]
metric = "swap"
column = 3
row = 2

(This is the packaged default, reproduced in full — see "Falls back to the packaged layout" below for what happens if you don't write any of this yourself.)

Key Type Required Description
metric string yes Which value this block shows. One of: cpu, ram, disk, load, cputemp, hot, swap. Anything else raises a ValueError on startup — a typo'd metric name is a real config mistake, not something that should silently vanish from the grid.
enabled bool no (defaults to true) false hides this block entirely — it isn't rendered as a blank cell, the grid just has one fewer entry.
column int yes Which column this block belongs to. Not capped at 3 — use as many columns as your box is wide enough for.
row int yes This block's own order within its column — not a literal index shared across every column. Columns don't need equal block counts (the packaged default's column 1 has 3 blocks, columns 2 and 3 have 2 each); only the relative order within one column matters, so gaps or non-contiguous numbers are harmless.
warning number no At or above this value, the block's own text renders in theme.warning (yellow by default) instead of theme.text. Omit alongside urgent to never threshold-color this block at all (see "Metrics without thresholds" below).
urgent number no At or above this value, renders in theme.urgent (red by default) — wins over warning if a value clears both bars.
label string no Overrides the displayed label text (defaults to the metric name, uppercased — "CPU", "CPUTEMP", ...).

Two enabled blocks can't share a (column, row) position_build_sysmon_blocks() raises on startup rather than letting one silently win. A disabled block can sit at an occupied position without conflict, since it never actually renders.

warning/urgent mean different units depending on the metric — a plain percent for cpu/ram/disk, degrees Celsius for cputemp/hot — the block already knows its own metric's unit, so there's nothing extra to configure there. RAM and DISK are colored by their real usage percent even though the displayed text is a used/available amount (e.g. "5.8/1.9 GiB"), not a bare percent — ram.percent/disk.percent from the underlying poll, not something re-derived from the rounded display text.

Metrics without thresholds (load, swap in the packaged default): a block with neither warning nor urgent set is never threshold-colored, always plain theme.text — this isn't a temporary gap, both have their own reasoning documented in VISION.md's R6 section: load's own "concerning" point depends on core count (a raw 2.0 means opposite things on a 2-core vs. 16-core machine) and isn't normalized yet; swap already has a documented false-positive risk (the kernel proactively swaps rarely-touched pages even with plenty of free RAM — a naive threshold would flag that normal housekeeping identically to real thrashing). You can still position and enable/disable these two like any other block, just without threshold coloring.

hot's own THROTTLED flag rides along on hot's own cell specifically, wherever you put it (or not at all, if you disable/remove that block) — it's a CPU-thermal signal (sysinfo.py's core_throttle_count delta between polls), not swap-related, despite an earlier version of this feature putting it on the swap block purely because that's where free row-space happened to be. In the packaged layout above, hot is the one cell with no fixed-width neighbor to its right (column 1's own 3rd row — the other two columns only have 2 entries), so a long THROTTLED marker can never get clipped there. If you reposition hot into a row that does share width with another column, a long value (its own describe_sensor() label, or the THROTTLED suffix) can get clipped — a consequence of your own layout choice, not something tuicc tries to work around for you.

Falls back to the packaged layout above when [sysmon] has no [[block]] entries at all (a fresh install, or an existing config.toml predating this section) — same .get()-with-fallback reasoning [audio] above established for a section landing after initial release. The moment you add even one [[sysmon.block]] entry, the packaged default stops applying entirely — list every block you want visible, not just the ones you're changing, or the rest will simply disappear.

The per-window list itself has no per-row config beyond [sysmon] visible_slots above — it always shows every real window across every workspace, sorted by resource drain (most CPU-intensive first, RSS as a tiebreak), not user-reorderable. NICE only ever writes 0-19 (positive/"nicer" only) — lowering niceness needs CAP_SYS_NICE/root tuicc doesn't assume it has.

sysmon itself is placed in the default layout preset already — no [[box]] entry needed unless you're building your own preset from scratch (see Layout above). quick_actions is the one built-in module that genuinely isn't placed by default — see that section above.


Clone this wiki locally