Skip to content

Latest commit

 

History

History
720 lines (530 loc) · 40.4 KB

File metadata and controls

720 lines (530 loc) · 40.4 KB

COFI Specification

Cofi is a keyboard-driven window switcher for X11/Linux with native GTK UI.

Scope. This document defines product behavior — what cofi does from the user's seat: window-list rules, search semantics, command catalog, keyboard model, slot/tile/run-mode behavior. For system shape (how the binary is wired internally) see docs/architecture.md. For design decisions and their history see docs/adr/. For domain terminology see docs/glossary.md.

Window List

Cofi displays a list of open windows on the system.

  • Show all normal application windows
  • Exclude docks, desktop backgrounds, and cofi's own window
  • Display format: 5 fixed-width columns, monospace font
    • Harpoon slot indicator (1 char: digit/letter if assigned, blank otherwise)
    • Desktop indicator: [0-9] or [S] for sticky
    • Instance name (20 chars, truncated)
    • Window title (67 chars, truncated)
    • Class name (18 chars, truncated)
  • Display order: bottom-up (first entry at bottom, fzf-style)
  • Selection indicator: > prefix on selected row

MRU Ordering

Windows are ordered by most recently used (MRU). The most recently focused window appears first.

  • When a window gains focus, it moves to the front of the list
  • When a window closes, it is removed without disrupting order of remaining windows

Alt-Tab Behavior

When cofi opens, the selection starts on the second entry (index 1) — the previously active window.

  • Pressing Enter immediately switches to that previous window, enabling quick Alt-Tab-style toggling
  • No swap of data structures is needed; this is purely about initial selection placement
  • On the Windows tab only, pressing . with an empty query repeats the last successful non-empty query-driven window activation for this session
  • Repeat re-runs the saved query against the current live window list and immediately activates the current top match
  • . with a non-empty query inserts a literal period normally
  • If no repeatable query has been stored yet, or the saved query has no current matches, . is a no-op and cofi stays open

Search

Real-time filtering as the user types. Case-insensitive.

Search Target

The search matches against the full displayed row as the user sees it — desktop indicator, instance name, window title, class name — joined into a single string. Display order = search order: if a column moves, what the scorer sees moves with it. This enables queries like "2ter" to find terminals on desktop 2, or combining any visible fields.

Scoring Algorithm

Single-pass fzf FuzzyMatchV2 (src/fzf_algo.c) over the joined display row. The algorithm itself rewards word-boundary and consecutive-character matches via internal position bonuses — there are no separate match stages. Higher scores rank higher; non-matches are dropped.

Examples of what the user effectively gets, all from one scoring pass:

  • "comm" → "Commodoro" (word-boundary boost)
  • "ddl" → "Daniel Dario Lukic" (initials, via consecutive boundaries)
  • "th" → "Thunderbird" (subsequence)

History. Until 2026-03-26 cofi used a multi-stage fzy cascade (word-boundary → initials → subsequence → fuzzy). It was replaced by a single fzf v2 pass on the full row. See ADR-0004.

Scoring Adjustments

Applied after the fzf score:

  • Windows on the current desktop receive a constant bonus.
  • Normal windows rank above special windows (docks, dialogs, etc.) — special windows pinned to bottom.

Behavior

  • Results update instantly as the user types.
  • Empty query shows all windows in MRU order (no scoring).
  • Selection resets to the default position (index 1) when the filter changes.

Keyboard Navigation

  • Up/Down (or Ctrl+k/j) — move selection through the window list
  • Enter — activate the selected window
  • Escape — close cofi without switching
  • Tab / Shift+Tab — cycle through visible tabs. Since TFD-545, tabs have three visibility states:
    • PINNED — always visible: Windows, Apps
    • SURFACED — shown once Tab-cycled into (e.g. via :show <tab> command), then dismissed on hide
    • HIDDEN — secondary tabs (Sessions, Workspaces, Harpoon, Matching, Layouts, Config, Hotkeys, Rules, Calc, Sinks, Run, Proc, Projects, Profiles, Bookmarks) not reached by Tab unless surfaced Tabs are surfaced programmatically by :show <verb> commands or explicit prefix flows; Tab/Shift+Tab only cycles PINNED + currently-SURFACED tabs.
  • Typing any character starts filtering immediately (no mode switch needed)

Window Appearance

  • Borderless (no title bar or window decorations)
  • Always on top of other windows
  • Skips taskbar and pager
  • Centered on screen
  • Auto-closes when it loses focus (configurable)

Layout

  • Window list (top) — scrollable text view
  • Search entry (bottom) — single-line input

Window Activation

When the user selects a window and presses Enter:

  • Switch to the window's desktop/workspace if different from current
  • Raise and focus the selected window
  • Close cofi

System Hotkeys

Cofi runs as a daemon and registers global hotkeys via XGrabKey on the X11 root window.

Hotkey bindings are stored in hotkeys.json as {key, command} pairs. Each binding maps a key combination to a cofi command string.

  • Default bindings:
    • Mod1+Tabshow windows!
    • Mod1+graveshow command!
    • Mod1+BackSpaceshow workspaces!
  • Hotkey format: Modifier+KeyName using X11 names (XStringToKeysym)
    • Modifiers: Mod1, Mod2, Mod3, Mod4, Control, Shift
  • CapsLock/NumLock variants grabbed automatically
  • Retry/Exit dialog on grab failure (e.g. another app owns the key)
  • When cofi is already visible, show-mode hotkeys switch mode inline (no reopen)
  • Pressing a hotkey while visible cancels any pending focus-loss close timer

Auto-Execute Marker (!)

Hotkey command strings support a ! suffix to control dispatch behavior:

  • With ! — command executes immediately. If it needs UI (e.g. overlay dialog), cofi opens automatically. If it doesn't need UI (e.g. :jw 3), it runs silently on the active X11 window.
  • Without ! — cofi opens with the command prefilled in the command line, letting the user edit or add arguments before pressing Enter.

Examples:

  • "show windows!" — opens cofi in windows mode immediately
  • "show run!" — opens cofi in run mode immediately
  • "jw 3!" — jumps to workspace 3 silently, no cofi window
  • "maw" — opens cofi with :maw prefilled, user types direction + Enter

Run Mode

Shell run entry triggered by typing ! in the search field or via show run.

  • Mode indicator changes from > to !
  • ! is indicator-only; run entry text is raw command text (no literal ! prefix)
  • Enter launches the trimmed command detached via the user's shell, then closes cofi
  • Bare ! (legacy tolerated by parser) or whitespace-only commands are a no-op
  • Run history is session-only, separate from command history, and navigable with Up/Down
  • Deleting back to empty exits run mode cleanly
  • extract_run_command() remains backward-compatible and still tolerates legacy !foo input
  • Run mode never updates repeat-last-query state

Calc Tab

Calculator modal triggered by typing = in the search field or via :calc.

  • Mode indicator changes from > to =
  • = is indicator-only; expression text is entered directly without a leading =
  • Enter evaluates the expression, copies the result to the clipboard, and keeps cofi open
  • Deleting back to empty exits calc mode cleanly
  • :calc <expression> evaluates an expression directly from command mode

Emoji Tab

Emoji picker triggered via :emoji or :show emoji.

  • 1,870 emoji entries sourced from the gemoji dataset
  • Empty query shows all emoji in MRU-first order (recently picked appear at top)
  • Typing filters by name, aliases, and keywords using weighted fzf scoring: name matches rank highest, then aliases, then keywords
  • Enter copies the selected emoji glyph to the clipboard and closes cofi
  • Pick history persists across restarts

Projects Tab

Hidden Projects surface triggered via :projects, :project, :tmux, :tx, :zj, :zellij, :show tmux, or :show projects.

  • Tmux sessions are listed from tmux list-sessions -F '#{session_name}\t#{session_windows}\t#{session_attached}'
  • Zellij sessions are listed from zellij list-sessions --short when zellij is available
  • Zoxide folders are listed from zoxide query -l when zoxide is available
  • The logical list keeps tmux sessions first, then zellij sessions, then zoxide folders; because cofi renders provider rows bottom-up, folders appear above sessions
  • Tmux sessions are marked [t]; zellij sessions are marked [z]; zoxide folders are marked [d]; locate fallback folders are marked [~]
  • Filtering is fzf-style over the full rendered row, including the marker, so tcofi and zcofi can disambiguate matching tmux and zellij session names
  • Missing session tools or empty session lists show a non-actionable status row only when there are no zoxide folder rows
  • When a query of 3+ characters is active, cofi also runs plocate / locate asynchronously against a basename glob derived from the query, optionally narrows candidates to configured search roots, then ranks accepted directory candidates in-process with the same fzf scorer plus a small shorter-path tiebreak; tmux/zellij/zoxide results still get a fixed priority bonus over locate rows in the merged list, and filesystem folders are appended as [~] rows
  • Enter on a tmux session launches a detected terminal detached from cofi, then runs tmux attach-session -t =<session>
  • Enter on a zellij session launches a detected terminal detached from cofi, then runs zellij attach --create <session>
  • Enter on a folder opens the folder in Caja when available, otherwise the system file opener
  • Delete on a tmux or zellij session opens a kill confirmation dialog; folder and status rows are ignored
  • F2 or Ctrl+R on a tmux session opens a rename dialog; zellij, folder, and status rows are ignored
  • Insert or Ctrl+N opens a new-session dialog; on folder rows it starts in that folder, otherwise it starts in $HOME
  • Shift+Insert opens the new-session dialog with zellij preselected
  • Ctrl+key assigns a per-Projects slot for the selected tmux session, zellij session, or folder
    • Ctrl+N is reserved for new session; use Ctrl+Shift+N to assign Projects slot n
    • Ctrl+R is reserved for rename; use Ctrl+Shift+R to assign Projects slot r
  • Alt+key recalls a per-Projects slot; session slots open the session and folder slots open the folder
  • Tmux session names are treated as exact tmux targets and shell-quoted before launch
  • Zellij session names are shell-quoted before attach or kill commands
  • Folder-derived session names use the folder basename with non [A-Za-z0-9_-] characters replaced by _
  • :tmux <session> attaches an existing session by exact name; it does not create a new tmux session
  • :tmux <key> recalls a Projects slot only when no exact session named <key> exists

Remote Sessions

The Projects tab supports remote host sessions via Ctrl+S.

  • Ctrl+S — open the remote host overlay to enter a hostname or user@host
  • After confirming the host, cofi fetches the remote tmux session list asynchronously and merges the results into the project list
  • Remote sessions are marked [REMOTE:host] and saved to ~/.config/cofi/projects.json; they reappear across restarts even when the host is unreachable
  • Enter on a saved remote session attaches the session via SSH, or creates a new one if it no longer exists
  • Additional overlay actions: new session, open sftp, open terminal in session directory
  • If the remote session is already open in a local window, cofi activates that window instead of launching a new terminal
  • Ctrl+D / Delete — shows a delete confirmation; removes the remote entry from the saved list

Single Instance

Only one cofi instance runs at a time.

  • Primary guard is a Unix domain socket at $XDG_RUNTIME_DIR/cofi.sock (fallback /tmp/cofi.sock)
  • cofi with no delegate flags returns already running + exit 1 when daemon exists
  • Delegate flags (--windows, --workspaces, --harpoon, --matching with --names alias, --command, --run, --applications) connect to the daemon, send a 1-byte opcode, and exit 0
  • If no daemon exists, cofi binds the socket, starts daemon mode, and applies the requested startup delegate mode
  • Stale socket path is recovered by connect-fail → unlink → bind retry

Daemon Startup Model

  • Daemon (default) — starts hidden, registers hotkeys, waits. Window shown only on hotkey press.
  • Delegate launch — any delegate flag opens the requested tab/mode via socket; no separate no-daemon mode exists.

Live Window List

The window list updates automatically in real-time:

  • New windows appear immediately
  • Closed windows disappear immediately
  • No polling — purely event-driven

Harpoon Slots

Assign windows to persistent slots for direct access.

  • 36 slots available: 0-9 and a-z
  • Ctrl+[key] — assign the selected window to that slot
    • Ctrl+j, Ctrl+k, Ctrl+u are reserved for navigation
    • Ctrl+Shift+j/k/u overrides the reservation and assigns anyway
  • Alt+[key] — activate the window in that slot directly (no exceptions)
    • Alt+digit behavior depends on digit_slot_mode (see below)
    • Alt+letter always activates global harpoon slots
  • Assignments persist across cofi restarts (stored in config)
  • When an assigned window closes, the slot attempts to reassign to a matching window (by class/title pattern)
  • Harpoon tab shows all current assignments
  • Harpoon indicators visible in the window list

Workspace Window Slots

When digit_slot_mode is set to per-workspace, Alt+1-9 activates the Nth window on the current workspace.

  • Only meaningfully visible windows are numbered. Excluded:
    • Minimized or shaded windows
    • Cofi's own window
    • Non-normal windows such as docks
    • Windows whose visible content area is too small after accounting for occlusion by windows above them in the stacking order
  • Visibility is based on the window content rect, not decoration leaks such as titlebars or borders
  • A window must pass all of these checks to receive a digit slot:
    • total visible content fraction meets the configured threshold
    • largest single visible content fragment also meets that threshold
    • largest visible fragment is at least 8x8 px
  • The threshold is configured by slot_occlusion_threshold as an integer percent (5 means 5%)
  • Windows are auto-numbered by screen position: left-to-right, top-to-bottom
  • No manual assignment needed — numbering updates automatically as windows move, resize, minimize, or restore
  • Alt+N is a no-op if the current workspace has fewer than N visible windows
  • Letter-based harpoon slots (Alt+a-z) remain global and unaffected

Slot Overlay Indicators

After slot assignment, numbered overlays appear briefly on each assigned window.

  • Overlays are independent X11 windows (visible even after cofi hides)
  • Overlay position is the center of the largest visible content fragment, not the raw window center
  • Duration controlled by slot_overlay_duration_ms config (default 750, 0 = disabled)
  • Catppuccin-themed: dark translucent background, opaque light text

Auto-Assignment

Slots auto-assign on every Alt+digit press — no manual step needed. Windows are re-scanned each time, so slots stay correct after moves/resizes.

Typical workflow: Alt+Tab (open cofi) → Alt+1 (jump). That's it.

Manual Assignment

Also available for explicit control:

  • CLI flagcofi --assign-slots
    • For use with external hotkeys (WM, sxhkd, etc.)
    • Assigns slots on the current workspace and exits silently

Digit Slot Mode (config)

digit_slot_mode controls what Alt+digit does:

  • default — global harpoon slots (existing behavior)
  • per-workspace — workspace-scoped window slots by position
  • workspaces — switch to workspace N

Replaces the old quick_workspace_slots boolean.

Matching

Assign custom names to windows that override the displayed title.

  • Display format: "custom_name - original_title"
  • Names persist across cofi restarts (stored in config)
  • When a named window closes, the name attempts to reassign to a matching window
  • Matching tab (Ctrl+E edit name, Ctrl+P edit pattern, Ctrl+D delete — works on both active and orphaned entries)
  • Searchable — custom names are included in filter matching

Rules Tab

Title-pattern automation rules triggered via :rules or :rl.

  • Rules map window-title glob patterns to cofi command sequences (e.g., auto-tile or workspace-assign windows matching a pattern)
  • Rules fire automatically on window-open and window-title-change events
  • Auto-restore for saved layouts is configured as a rule whose action is rl (e.g., cofi* → rl). Layout payload lives in layouts.json, keyed by match_id; the rule controls when to restore, the layout controls what to restore. Composable with other actions: rl,sb+,ab+ restores geometry then sets sticky + always-above.
  • Ctrl+A — add a new rule
  • Ctrl+E — edit the selected rule commands
  • Ctrl+P — edit the selected rule pattern
  • Ctrl+O — toggle the selected rule's once flag
  • Ctrl+N — toggle the selected rule's new-only flag
  • Ctrl+D — delete the selected rule
  • Ctrl+X — replay the selected rule against all currently open matching windows (explicit, stateless)
  • Ctrl+Shift+X — replay all stored rules in stored order against all currently open windows

Saving or editing a rule does not immediately replay it; changes apply only to subsequent window transitions. Use Ctrl+X / Ctrl+Shift+X for one-shot replay.

The flag column shows rule firing mode at a glance: O = once-armed (will fire once then disarm), Ø = once-fired (already consumed), N = new-only (fires only on new windows / title-change transitions, not on existing-window dispatch).

Advanced mode is enabled by rules_show_all_tags. When enabled, :rules shows a trailing tag column naming the owning subsystem (for example geom). Geom-tagged rules are hidden from :rules by default.

  • Tagged rules are read-only in :rules; edit/delete/toggle actions fall through so mutations happen in the owning tab instead. The shortcut hint reflects this dynamically when a tagged row is selected.

Layout-rule migration note (TFD-784 PR2): tag only the restore rules you want geom to manage with "tag": "geom" in ~/.config/cofi/rules.json. Hand-authored/composite user rules with custom commands should remain untagged unless you specifically want them hidden from :rules and synced with layout enable/disable state.

Command Mode

Vim-style command entry triggered by typing : in the search field.

  • Mode indicator changes from > to :
  • Supports compact no-space syntax: :cw2 is equivalent to :cw 2
  • Command history: last 10 commands, navigable with Up/Down
  • Help available via :help with paged output
  • Help/config display persists while typing (dismissed on Esc)
  • Prefix candidate strip (TFD-546): while typing a command verb prefix, the second display header line shows up to 16 matching candidate verbs sorted by length then alphabetically. The current best match is wrapped in [...]. Candidates update live and disappear once the prefix resolves to a unique verb.
  • When entered from hidden/delegated flows, target selection is pinned by command_target_id: capture active window ID before show_window(), then enter_command_mode() resolves that ID in the current filtered list

Window Commands

  • :cw [N|dir] (:change-workspace) — move selected window to workspace N or direction (h/j/k/l)
  • :pw (:pull-window, :p) — pull selected window to current workspace
  • :cl (:close-window, :c) — close selected window
  • :save-layout (:sl) — save the selected window's position, size and workspace
  • :restore-layout (:rl) — restore the saved position, size and workspace
  • :delete-layout (:dl) — forget the saved layout for the selected window
  • :sw (:swap-windows) — swap two windows (positions and sizes)
  • :maw [N|dir] (:move-all-to-workspace) — move all windows from current workspace to target
  • :mw [toggle|on|off] (:max, :maximize-window [toggle|on|off]) — set maximize on the selected window (default toggle)
  • :miw (:min, :minimize-window) — toggle minimize (restore if already minimized, closes cofi)

Tiling Commands

  • :tw or :t (:tile-window) — tile selected window
    • Halves: L, R, T, B (50%)
    • Quarters: l1, r1, l2, r2 (25%)
    • Two-thirds: L2, R2 (66%)
    • Three-quarters: L3, R3 (75%)
    • Center: c with optional size
    • Grid positions: 2x2 or 3x2

Workspace Commands

  • :jw [N|dir] (:jump-workspace, :j) — jump to workspace N or direction (h/j/k/l)
  • :rw [N] (:rename-workspace) — rename workspace N (current if omitted)

Window Property Commands

  • :sb [toggle|on|off] (:skip-taskbar [toggle|on|off]) — set skip taskbar (default toggle)
  • :aot [toggle|on|off] (:at, :always-on-top) — set always on top (default toggle)
  • :ab [toggle|on|off] (:always-below) — set always below (default toggle)
  • :ew [toggle|on|off] (:every-workspace) — set sticky (all desktops, default toggle)

Monitor Commands

  • :tm [N] (:toggle-monitor [N]) — move window to the next monitor, or to explicit monitor N when provided (for example :tm0, :tm1)
  • :hm [toggle|on|off] (:horizontal-maximize-window [toggle|on|off]) — set horizontal maximize on the selected window (default toggle)
  • :vm [toggle|on|off] (:vertical-maximize-window [toggle|on|off]) — set vertical maximize on the selected window (default toggle)

Mouse Commands

  • :mouse (:m, :ma, :ms, :mh) — mouse control with subcommand: away, show, hide

Naming Commands

  • :an (:assign-name, :n) — assign custom name to selected window (cofi-internal alias)
  • :rename <title> (:rn) — force the selected window's X11 title (_NET_WM_NAME/WM_NAME); apps that self-title may overwrite it

Configuration Commands

  • :set <key> <value> — set a config option at runtime (also accepts key=value)
  • :config (:conf, :cfg) — switch to the interactive Config tab
  • :show (:s) — switch cofi mode: windows, command, run, workspaces, harpoon, matching (with names alias), config, rules, apps/applications, bluetooth, emoji, projects, calc, proc, sinks, sessions, profiles, bookmarks
  • :rules (:rl) — switch to the interactive Rules tab

Hotkey Management Commands

  • :hotkeys (:hotkey, :hk) — show all hotkey bindings
  • :hotkeys <key> <command> — bind a hotkey (e.g. :hotkeys Mod4+1 jw 1)
  • :hotkeys <key> — unbind a hotkey
  • Optional sugar keywords accepted: list, set, add, del, rm, remove

Slot Commands

  • :js N (:jump-slot) — jump to the Nth visible window on the current workspace by screen position (1–9)

Help

  • :help (:h, :?) — show command help with paged output

Tiling

Position and resize the selected window to predefined screen regions.

  • Half screen: left, right, top, bottom (50%)
  • Quarters: four corners (25%)
  • Two-thirds: left or right (66%)
  • Three-quarters: left or right (75%)
  • Center: with configurable size
  • Grid positions: 2x2 or 3x2 layout
  • Multi-monitor aware: tiles relative to the window's current monitor
  • Respects work area (excludes panels, docks, taskbars)
  • Respects window size hints (e.g., terminal minimum sizes)

Apps Tab

Launch installed desktop applications from a dedicated Apps tab.

  • Data source: XDG desktop applications via GLib/GIO app-info APIs
  • Excludes hidden / invalid / NoDisplay apps
  • Enter launches the selected app detached and closes cofi
  • --applications starts directly on the Apps tab
  • :show apps switches to the Apps tab
  • \ surfaces the Apps tab from the entry field

System Actions

Six system actions are available as fixed entries in the Apps tab:

  • Lock — screen lock via shell fallback chain: xdg-screensavermate-screensaver-commandxscreensaver-commandloginctl → logind D-Bus Session.Lock
  • Suspend / Hibernate / Logout / Reboot / Shutdown — via logind D-Bus (org.freedesktop.login1)

Apps Matching And Ranking

Apps tab matching is local to the Apps launcher and does not reuse Windows-tab MRU/fuzzy ranking.

  • Ranking priority: name > generic_name > keywords
  • generic_name and keywords are token-matched to avoid cross-token false positives
  • Alphabetical order is only used as a tie-breaker within the same ranking tier

Path Tab

Launch executables discovered on the user PATH from a dedicated hidden tab.

  • :show path switches to the Path tab
  • :path, :binaries, :bin, and :exe surface the same tab
  • $ surfaces the Path tab from the entry field
  • Enter launches the selected executable in a terminal by default (TFD-564)
  • Hidden by default; it is not Tab-cycled until surfaced

Path Discovery And Ranking

  • Query filters the cached PATH binary list by substring (case-insensitive pre-filter), then ranks surviving matches by fzf score descending.
  • Cache cap: 4096 entries (MAX_PATH_BINS). On overflow a single warning is logged; later PATH entries are dropped.
  • Filter output cap: 512 entries (MAX_APPS). Scoring runs across ALL substring-matched entries first; the cap is applied at copy-out after sorting, not during scoring.
  • All PATH directories are watched via GFileMonitor (cap: 64 directories). File creates/deletes in PATH dirs update the cache incrementally.
  • Basename dedupe: when the same binary name appears in multiple PATH dirs, the first occurrence (highest-priority PATH dir) wins.

Terminal Detection

When launching a terminal app or Path-tab executable, the terminal is detected via this priority chain:

  1. $TERMINAL env var (if the named binary resolves in PATH)
  2. Desktop-environment configured terminal:
    • MATE, GNOME, Cinnamon → gsettings get org.gnome.desktop.default-applications.terminal exec
    • KDE → konsole (hardcoded)
    • XFCE → xfconf-query -c xfce4-terminal -p /default-terminal (falls back to xfce4-terminal binary if the query returns nothing)
  3. x-terminal-emulator (Debian alternatives system), then hardcoded candidate list: mate-terminal, gnome-terminal, konsole, alacritty, kitty, foot, wezterm, urxvt, xterm
  4. Final fallback: xterm

Process Detachment

Desktop-app launches use detach_launch_properly; Path-tab executable launches use the terminal detach helper:

  • Primary: systemd-run --user --scope -- <argv> — places the launched process in its own transient systemd scope, completely outside cofi's cgroup.
  • Fallback: fork + setsid + double-fork + execvp. An errno-pipe (pipe() + FD_CLOEXEC on write end) propagates exec failure back to the parent — a non-empty pipe read means execvp failed.
  • All launched apps survive cofi stop/restart.

Desktop Entry Launch Behavior

  • Terminal=true entries (htop, vim, etc.) are launched as {term, -e, sh, -c, cmd}. The explicit sh -c wrapper ensures consistent behavior across all terminals regardless of how they split the -e argument.
  • Terminal=false (GUI) entries are parsed with g_shell_parse_argv and spawned directly via detach_launch_argv_array — no shell is involved. Shell metacharacters ($(), backticks, ;) in Exec= do not execute.
  • Malformed Exec= strings (unbalanced quotes) are logged and rejected without crashing.

Workspace Management

View and interact with workspaces via the Workspaces tab.

  • List all workspaces with window counts
  • Switch to a workspace by selecting it and pressing Enter
  • Rename workspaces with custom names (persistent)
  • Display as grid (configurable rows via workspaces_per_row, 0 = single row)
  • Directional navigation: HJKL and arrow keys in workspace overlays

Sessions Tab

Claude and Codex coding-session browser triggered via :sessions or :session.

  • Indexes active and archived Claude/Codex project session directories on the local filesystem
  • Query syntax: terms | refine — the left side of | starts a live cancellable corpus search across session content; the right side refines the already-returned session rows
  • Enter resumes the selected session
  • Ctrl+R — rename the selected session (available for Claude/Codex source sessions)
  • Ctrl+D / Delete — delete the selected session with a confirmation prompt

Sinks Tab

Audio output sink selector triggered via :sinks or :sink.

  • Lists all available PulseAudio/PipeWire output sinks
  • Enter activates the selected sink as the system default audio output and closes cofi
  • Ctrl+[key] — assign the selected sink to a per-Sinks slot
  • Alt+[key] — recall and activate a saved sink slot without opening the tab
  • :sinks @SLOT or :sinks SINK — activate a sink directly from command mode

Proc Tab

Process manager triggered via :proc or :ps.

  • Lists running system processes, refreshed automatically every 1.5 seconds
  • Filtering narrows by process name or PID in real time
  • Enter signals the selected process

Profiles Tab

Browser profile launcher triggered via :profiles, :chrome, or :browser.

  • Lists Chrome (and compatible) browser profiles read from the browser's local state file
  • Enter opens the selected profile in a dedicated browser window and closes cofi
  • Ctrl+[key] — assign the selected profile to a per-Profiles slot
  • Alt+[key] — recall and launch a saved profile slot without opening the tab
  • :profiles @SLOT or :profiles PROFILE — launch a profile directly from command mode
  • Browser executable resolved from $PATH; falls back to google-chrome-stable

Bookmarks Tab

Chrome bookmarks across every discovered profile, triggered via :bookmarks or :bm.

  • Walks ~/.config/google-chrome/<profile>/Bookmarks for every Chrome profile reported by browser-profiles discovery, skipping Guest Profile.
  • Default profile bookmarks appear first, then remaining profiles in profile-tab order (active_time desc, case-insensitive name tie-break); within each profile, rows follow the roots.{bookmark_bar, other, synced} walk order.
  • Each row shows five cells: [bm], profile label, bookmark name, folder breadcrumb (display-truncated to 10 chars with leading so the deepest folder stays visible), and URL.
  • Match-string corpus is [bm] <profile_label> <name> <folder_breadcrumb> <url>. Scoring: bookmarks share the windows tab's tiered ranker via tier_score_string over the combined match-string corpus ("[bm] profile_label name folder_breadcrumb url"). A direct word-boundary match (e.g. typing a profile name that appears verbatim in the row) ranks above any indirect fuzzy match regardless of field. Load-order is the tiebreaker.
  • URL scheme allow-list: http, https, ftp, file. Skips javascript:, chrome://, chrome-extension://, data:, and empty URLs.
  • Row cap: 20000 across all profiles. Overflow logs a single WARN and stops appending; remaining profiles are partially included.
  • Enter opens the URL in a new Chrome window (chrome --profile-directory=<dir> --new-window <url>); Shift+Enter opens without --new-window so Chrome reuses an existing window/process (default behavior).
  • Shortcut hint: Enter=Open new window Shift+Enter=Reuse Chrome Ctrl+key=Assign slot Alt+key=Recall slot
  • Ctrl+[key] assigns the selected bookmark to a per-Bookmarks slot; Alt+[key] recalls and launches a saved bookmark slot without opening the tab.
  • :bookmarks @SLOT or :bookmarks QUERY launches a bookmark directly from command mode. Slot payload format: bookmark:chrome:<profile_dir>:<url> (colons inside the URL are preserved). Row identity: bookmark:<profile_dir>:<url>.
  • No caching, no inotify: every tab entry re-reads the Bookmarks files.

Tabs

Nineteen tabs exist; visibility is controlled per-tab (TFD-545):

  • PINNED (always shown, always Tab-reachable): Windows, Apps
  • HIDDEN by default (only surfaced by :show <verb> or explicit flows): Path, Bluetooth, Files, Sessions, Workspaces, Harpoon, Matching, Layouts, Config, Hotkeys, Rules, Calc, Sinks, Run, Proc, Projects, Profiles, Bookmarks

Tab/Shift+Tab cycles PINNED tabs plus any currently-SURFACED tabs. Secondary tabs do not appear in Tab cycling until surfaced.

  1. Windows — main window list with search and MRU ordering (PINNED)
  2. Apps — installed desktop application launcher + system actions (PINNED)
  3. Path — PATH executable launcher (HIDDEN by default)
  4. Bluetooth — paired BlueZ device list; Enter toggles connect/disconnect (HIDDEN by default)
  5. Files — fuzzy file finder under $HOME via fd (HIDDEN by default)
  6. Sessions — Claude/Codex session search and resume (HIDDEN by default)
  7. Workspaces — workspace list and management (HIDDEN by default)
  8. Harpoon — harpoon slot assignments (Ctrl+P edit pattern, Ctrl+D delete) (HIDDEN by default)
  9. Matching — custom window name assignments (Ctrl+E edit name, Ctrl+P edit pattern, Ctrl+D delete) (HIDDEN by default)
  10. Layouts — saved window layouts (Ctrl+D/Delete delete, Ctrl+L workspace restore, Ctrl+T enable/disable, Ctrl+P edit pattern) (HIDDEN by default)
  11. Config — all config options (Ctrl+T toggle/cycle, Ctrl+E edit) (HIDDEN by default)
  12. Hotkeys — hotkey bindings (Ctrl+E edit, Ctrl+D delete) (HIDDEN by default)
  13. Rules — title-pattern automation rules (Ctrl+A add, Ctrl+E edit commands, Ctrl+P edit pattern, Ctrl+D delete, Ctrl+X replay selected, Ctrl+Shift+X replay all) (HIDDEN by default)
  14. Calc — calculator modal (HIDDEN by default)
  15. Sinks — audio sink selection (HIDDEN by default)
  16. Run — command runner modal (HIDDEN by default)
  17. Proc — process manager (HIDDEN by default)
  18. Projects — tmux/zellij sessions and zoxide folders (HIDDEN by default)
  19. Profiles — browser profile launcher (HIDDEN by default)
  20. Bookmarks — Chrome bookmarks across every profile (HIDDEN by default)
  • Selection state is preserved per tab when switching

Bluetooth Tab

Bluetooth is a hidden-by-default provider tab surfaced by :bt, :bluetooth, or :show bluetooth.

  • Scope is paired BlueZ devices only. v1 does not scan, pair, unpair, or manage adapter power state.
  • The first column is a persistent connected-state glyph: 🟢 means the device is currently connected, means it is not.
  • The trailing state column is transient feedback only: […connecting…], [connected], [failed], and [disconnected] flash briefly, then clear.
  • Devices are listed in deterministic alphabetical order by alias, with object path as the tiebreaker for identical aliases.
  • The adapter column is hidden when exactly one powered adapter exists, and is shown when multiple powered adapters are present.
  • Enter toggles connect/disconnect. c forces connect, d forces disconnect, and r refreshes immediately.
  • Refresh is async and poll-driven in v1; the active tab re-queries BlueZ on a 3-second cadence without blocking cofi.

Files Tab

Files is a hidden-by-default provider tab surfaced by :files or :show files.

  • Scope is any file under $HOME, discovered by an async fd scan and cached once per daemon lifetime until refreshed.
  • Hidden files are included, but fd still honors .gitignore and .fdignore by default, which naturally suppresses many build and dependency artifacts.
  • Rows show basename first and full path second.
  • Enter opens the selected file via xdg-open and closes cofi.
  • r cancels any in-flight scan and refreshes the cache.

Configuration

Stored in ~/.config/cofi/:

  • options.json — application settings
    • Window position (9 positions: top, center, bottom, corners)
    • Close on focus loss (on/off)
    • Workspace grid columns (workspaces_per_row, 0 = single row)
    • Tiling grid columns (2 or 3)
    • Digit slot mode (default / per-workspace / workspaces)
    • Slot overlay duration in ms (default 750, 0 = disabled)
    • Ripple effect (on/off, default on)
    • Projects executable overrides:
      • projects.tmux_path
      • projects.zellij_path
      • projects.zoxide_path
      • projects.file_explorer_path
      • Empty means resolve the executable from cofi's process PATH; non-empty values must be absolute executable file paths.
  • hotkeys.json — system hotkey bindings as {key, command} pairs (up to 64)
    • Managed via :hotkeys command or by editing the file directly
    • Auto-generated with default show-mode bindings on first run
  • harpoon.json — harpoon slot assignments with match patterns
  • matching.json — custom window matching entries
    • class_name / instance / type are optional anchors. When set, they must exactly match the live window property; when empty, they are unconstrained.
    • Auto-created entries (:sl, harpoon-assign, name-assign) capture these three anchors from the source window for precise matching.
    • User-authored rules currently remain title-focused; future editing paths can deliberately leave anchors empty for title-only behavior.
  • rules.json — window-title rules (match_id, pattern, commands) in stored order
    • Legacy pattern-only rules migrate on load by creating a matching MatchEntry (wildcard semantics, empty class/instance/type anchors); next save writes match_id while retaining pattern as a compatibility cache for one release.

Runtime config changes via :set <key> <value> are saved to options.json immediately. View current config with :config.

CLI Arguments

  • --align ALIGNMENT — window position (center, top, bottom, top_left, top_right, left, right, bottom_left, bottom_right)
  • --no-auto-close — keep cofi open when it loses focus
  • --windows / -W — delegate to Windows tab
  • --workspaces — delegate to Workspaces tab
  • --harpoon — delegate to Harpoon tab
  • --matching — delegate to Matching tab (--names alias kept for compatibility)
  • --command — delegate to command mode
  • --run — delegate to run mode
  • --applications — delegate to Apps tab
  • --assign-slots — assign workspace window slots and exit
  • --log-level LEVEL — set log verbosity (trace, debug, info, warn, error, fatal)
  • --log-file FILE — write logs to file
  • --no-log — disable logging
  • --version — show version
  • --help — show usage
  • --help-commands / -H — show command mode help

Window Highlight (Ripple Effect)

When a window is activated (via Alt-Tab, harpoon, workspace switch), a visual ripple highlights the target window.

  • Circle ripple: expanding ring centered on the target window
  • Vsync-locked 60fps via GdkFrameClock + Cairo on a single ARGB popup window
  • 200ms duration with ease-out easing
  • Ring expands from 10% to 30% of screen height, 16px stroke width
  • Color: light blue-white (#f0f0ff), full brightness for 80% then fades out
  • Cancelled immediately on workspace switch to prevent accumulation
  • Configurable: ripple_enabled in options.json (default true)
  • Graceful fallback: disabled when no ARGB visual available (no compositor)
  • HiDPI-safe: X11 pixel coordinates for positioning, GDK logical coordinates for drawing

Compact Command Syntax

Commands support no-space compact form: :cw2 = :cw 2, :mawk = :maw k. For window-state commands (sb, ab, aot/at, ew, mw, hm, vm), compact +/- are supported: :sb+ = :sb on, :ew- = :ew off, :mw+ = :mw on, :hm- = :hm off.

Rules tab save/edit/delete are persistence-only (rules.json): saving a rule does not immediately replay it. Automatic runtime rule behavior remains transition-based. Manual replay actions are explicit and stateless: Ctrl+X replays selected rule against all currently open matching windows; Ctrl+Shift+X replays all stored rules in stored order against current open windows.

  • Compact forms defined in a data-driven table (COMPACT_FORMS) mapping command names to accepted suffix characters
  • Parser picks the longest matching command name to resolve prefix ambiguity
  • Exact command names are never split: :maw stays as command "maw" with no arg, not "m" + "aw"
  • Aliases participate in matching: :j3 matches alias "j" for "jw", producing "jw" + "3"

Autostart

The maintained startup path is the systemd user service installed by mise run install / make install or mise run install-dev / make install-dev. These install to /usr/local/bin by default. The explicit local variants are mise run install-local / make install-local and mise run install-dev-local / make install-dev-local. The service template is scripts/cofi.service.