Add distrobox container integration for tmux, zellij, and fish#10
Add distrobox container integration for tmux, zellij, and fish#10
Conversation
Auto-hide statusbar when only 1 window is open, show when 2+. Add prefix+b toggle, middle-click to close tab, click interactions for status-left/right areas, and styled session indicator.
- Fish abbreviations: db, dbe, dbl, dbs, dbrm, dbc for distrobox commands - Fish function: container-fzf with fzf picker, supports tmux/zellij flags - tmux: prefix+e enters container in current pane, prefix+E in new window - tmux: container-aware splits (prefix+h/v inherit CONTAINER_ID) - tmux: status bar shows container name for active pane - zellij: session mode 'e' opens container picker in floating pane - zellij: container.kdl layout with host + picker tabs - Starship: container module shows name in prompt when inside distrobox
- Single keybind (Ctrl+o, e) opens floating picker with three actions: enter=current pane, alt-s=tiled split, alt-t=new tab - Toggle floating off before creating panes to fix focus issues - Use alt-s/alt-t instead of ctrl-s/ctrl-t to avoid zellij interception - Add close_on_exit to picker pane for clean auto-dismiss
- enter: writes command to current pane - alt-s: new tiled split with distrobox as pane command (direct) - alt-t: new tab via temp KDL layout with compact-bar/status-bar chrome - Remove broken --in-place and break-pane approaches
All three zellij picker actions now launch distrobox as a direct pane command: enter=floating, alt-s=tiled split, alt-t=new tab via temp layout
- Add --tmux flag: enter=new window, alt-h=hsplit, alt-v=vsplit - Remove old single-action flags (--new-window, --split-h, --split-v) - Simplify tmux popup script to one-line wrapper calling container-fzf --tmux - Single prefix+e keybind replaces prefix+e and prefix+E - Both multiplexers now use the same fzf --expect pattern
There was a problem hiding this comment.
Pull request overview
Adds distrobox container awareness and a unified interactive picker across shell (fish), tmux, and zellij, with prompt/status indicators to surface the active container.
Changes:
- Introduces
container-fzf(fish) as a multi-action fzf-based distrobox container picker with tmux and zellij modes. - Integrates container picker + container-aware splits + status indicator into tmux configuration via new helper scripts.
- Adds zellij keybind + optional layout for container workflows; adds distrobox fish abbreviations and a Starship container module.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
dot_config/zellij/layouts/container.kdl |
New zellij layout that opens a host tab and a container picker tab. |
dot_config/zellij/config.kdl.tmpl |
Adds a session-mode keybind to launch the container picker in a floating pane. |
dot_config/tmux/tmux.conf.tmpl |
Adds tmux keybinds for the picker, container-aware splits, status-right container indicator, and status auto-hide hooks. |
dot_config/tmux/scripts/executable_container-pick-send.fish |
Thin tmux popup wrapper that runs container-fzf --tmux. |
dot_config/tmux/scripts/executable_container-split.sh |
New script to detect CONTAINER_ID and split into the same container when applicable. |
dot_config/tmux/scripts/executable_container-status.sh |
New status script to display container name/icon in tmux status-right. |
dot_config/starship.toml |
Adds Starship [container] module and inserts it into the prompt format. |
dot_config/fish/functions/container-fzf.fish |
New core picker function with tmux/zellij actions and preview. |
dot_config/fish/conf.d/zz_04_abbr.fish |
Adds distrobox-related fish abbreviations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for pid in $(pgrep -P "$PANE_PID" 2>/dev/null) $PANE_PID; do | ||
| if [ -r "/proc/$pid/environ" ]; then | ||
| cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep '^CONTAINER_ID=' | head -1 | cut -d= -f2-) |
There was a problem hiding this comment.
With set -euo pipefail, the cid=$(... | grep '^CONTAINER_ID=' | ...) pipeline will terminate the script whenever CONTAINER_ID isn’t present (grep exit 1), which is expected on the host and for many child processes. That prevents the intended host fallback split path from running. Make the lookup tolerant of “not found” (e.g., grep ... || true / sed -n extraction) and guard pgrep returning 1 when there are no children.
| for pid in $(pgrep -P "$PANE_PID" 2>/dev/null) $PANE_PID; do | |
| if [ -r "/proc/$pid/environ" ]; then | |
| cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep '^CONTAINER_ID=' | head -1 | cut -d= -f2-) | |
| children_pids=$(pgrep -P "$PANE_PID" 2>/dev/null || true) | |
| for pid in $children_pids $PANE_PID; do | |
| if [ -r "/proc/$pid/environ" ]; then | |
| cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | sed -n 's/^CONTAINER_ID=//p' | head -1) |
| if set -q _flag_tmux; or set -q _flag_zellij | ||
| set key $fzf_output[1] | ||
| set selected $fzf_output[2] | ||
| else |
There was a problem hiding this comment.
In tmux/zellij mode, fzf --expect outputs an empty first line when Enter is pressed. Fish command substitution drops empty fields, so $fzf_output[1] can become the selection (not the key) and $fzf_output[2] becomes empty, causing the Enter action to be skipped. Capture fzf output without whitespace-splitting (e.g., read the first two lines explicitly, or collect raw output and split while preserving empties, or use fzf --print0 + string split0).
| tmux split-window -h -c "#{pane_current_path}" "distrobox enter $container_name" | ||
| case "alt-v" | ||
| tmux split-window -v -c "#{pane_current_path}" "distrobox enter $container_name" | ||
| case "" | ||
| tmux new-window -n "$container_name" "distrobox enter $container_name" |
There was a problem hiding this comment.
These tmux commands interpolate $container_name into a single command string. If the container name contains spaces or shell-special characters, it can break the command or be interpreted by the shell. Prefer passing the command and args as separate arguments (and use distrobox enter -- "$container_name") instead of building one shell string.
| zellij action new-pane -d down --name "$container_name" -- distrobox enter $container_name | ||
| case "alt-t" |
There was a problem hiding this comment.
Same quoting/argument-separation issue here: $container_name is passed as unquoted args to zellij action ... -- distrobox enter $container_name. Use -- and quote the name (or otherwise ensure it’s passed as a single argument) to avoid breakage with non-trivial container names.
| # Default (no multiplexer flag): enter the container in the current shell | ||
| distrobox enter $container_name |
There was a problem hiding this comment.
distrobox enter $container_name should pass the container name as a single argument (e.g., distrobox enter -- "$container_name") so names with spaces/special characters don’t get split or glob-expanded by fish.
| # Container-aware splits: if current pane is inside a container, new splits | ||
| # auto-enter the same container. Falls back to normal split on host. | ||
| bind h run-shell "~/.config/tmux/scripts/container-split.sh -h" | ||
| bind v run-shell "~/.config/tmux/scripts/container-split.sh -v" |
There was a problem hiding this comment.
This re-binds prefix+h/prefix+v to run the container-aware split scripts, but the config still contains earlier bindings for bind h split-window ... / bind v split-window ... (lines 22–23) which will now be overridden. Consider removing or updating the earlier bindings to avoid confusing duplicates and make it clear that these keys are intentionally repurposed.
| // Usage: zellij --layout container | ||
| // | ||
| // Opens a host tab and a container picker tab. | ||
| // To create a layout with a specific container, use: |
There was a problem hiding this comment.
The comment claims this creates a layout "with a specific container", but the example command shown doesn’t pass any container selection/name into the layout. Either adjust the wording (it currently just names a session) or document the actual mechanism for preselecting a container if that’s intended.
| // To create a layout with a specific container, use: | |
| // To create a named session using this layout, use: |
dot_config/zellij/config.kdl.tmpl
Outdated
| bind "w" { SearchToggleOption "Wrap"; } | ||
| } | ||
| session { | ||
| // Container picker: enter=current pane, alt-s=split, alt-t=new tab |
There was a problem hiding this comment.
The comment says enter=current pane, but container-fzf --zellij uses Enter to open a floating pane (and then closes the picker). Please update the comment so it matches the actual behavior.
| // Container picker: enter=current pane, alt-s=split, alt-t=new tab | |
| // Container picker: enter=floating pane, alt-s=split, alt-t=new tab |
| for pid in $(pgrep -P "$PANE_PID" 2>/dev/null) $PANE_PID; do | ||
| if [ -r "/proc/$pid/environ" ]; then | ||
| cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep '^CONTAINER_ID=' | head -1 | cut -d= -f2-) | ||
| if [ -n "$cid" ]; then |
There was a problem hiding this comment.
With set -euo pipefail, the cid=$(... | grep '^CONTAINER_ID=' | ...) pipeline will exit the script when grep finds no match (exit 1), which is a normal case on the host and for many processes. That will make tmux status rendering fail instead of just producing an empty string. Make the lookup non-fatal (e.g., grep ... || true / use sed -n extraction) and also guard pgrep returning 1 when there are no children.
| for pid in $(pgrep -P "$PANE_PID" 2>/dev/null) $PANE_PID; do | |
| if [ -r "/proc/$pid/environ" ]; then | |
| cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep '^CONTAINER_ID=' | head -1 | cut -d= -f2-) | |
| if [ -n "$cid" ]; then | |
| children_pids=$(pgrep -P "$PANE_PID" 2>/dev/null || true) | |
| for pid in $children_pids $PANE_PID; do | |
| if [ -r "/proc/$pid/environ" ]; then | |
| cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep '^CONTAINER_ID=' | head -1 | cut -d= -f2- || true) | |
| if [ -n "${cid:-}" ]; then |
| # Inside a container — new split enters the same container | ||
| tmux split-window "$SPLIT_DIR" -c "#{pane_current_path}" "distrobox enter $CONTAINER_ID" |
There was a problem hiding this comment.
The tmux split-window command builds a shell command string using the untrusted CONTAINER_ID environment value ("distrobox enter $CONTAINER_ID"), which enables command injection from inside the container onto the host. A process in the container can set CONTAINER_ID to contain shell metacharacters (for example foo; rm -rf /), so when you trigger a container-aware split tmux will run the injected commands in a new host shell. To fix this, treat CONTAINER_ID as untrusted: validate it against the expected container name format and/or invoke distrobox enter with CONTAINER_ID passed as a separate, safely-quoted argument so shell metacharacters cannot change the command structure.
| # Inside a container — new split enters the same container | |
| tmux split-window "$SPLIT_DIR" -c "#{pane_current_path}" "distrobox enter $CONTAINER_ID" | |
| # Validate CONTAINER_ID to avoid command injection | |
| if [[ "$CONTAINER_ID" =~ ^[A-Za-z0-9._-]+$ ]]; then | |
| # Inside a container — new split enters the same container | |
| tmux split-window "$SPLIT_DIR" -c "#{pane_current_path}" "distrobox enter \"$CONTAINER_ID\"" | |
| else | |
| # Invalid CONTAINER_ID detected — fall back to a normal host split | |
| tmux split-window "$SPLIT_DIR" -c "#{pane_current_path}" | |
| fi |
- Move container-fzf to ~/.local/bin/ as a bash script (works from any shell) - Remove fish-only container-fzf.fish function - Convert container-pick-send from .fish to .sh - Update tmux/zellij configs to call container-fzf directly (no fish wrapper) - Fix fzf preview to use explicit sh -c to avoid fish /bin/fish interference
Unify hotkeys so both multiplexers use the same mental model: - Enter = primary action (new window in tmux, new tab in zellij) - Alt-s = split pane - Alt-f = floating pane (zellij only, N/A for tmux) Replaces the old alt-h/alt-v (tmux) and alt-t (zellij) keybinds.
Alt+f is already bound to ToggleFloatingPanes in zellij's shared_except keybinds, so it never reaches fzf. Users can toggle floating with Alt+f after opening a container pane.
- container-split.sh / container-status.sh: replace grep with sed -n to avoid exit code 1 under set -euo pipefail when no match found; add || true to pgrep calls - container-split.sh: sanitize CONTAINER_ID against [A-Za-z0-9._-]+ before interpolation to prevent command injection from inside containers; pass distrobox as separate args instead of single shell string - tmux.conf.tmpl: remove duplicate prefix+h/v plain split bindings (overridden by container-aware splits below) - Fix stale comments in tmux.conf.tmpl and container.kdl
Summary
container-fzf) with multi-action fzf interface for both tmux and zellijdb,dbe,dbl,dbs,dbrm,dbc)tmux (
prefix+e)Container-aware splits (
prefix+h/v) detectCONTAINER_IDin the current pane and auto-enter the same container in new splits.zellij (
Ctrl+o, e)All actions launch
distrobox enteras the pane's command directly — no shell wrapper or text injection.Files changed
fish/conf.d/zz_04_abbr.fishfish/functions/container-fzf.fish--tmuxand--zellijmodesstarship.tomltmux/scripts/container-pick-send.fishtmux/scripts/container-split.shtmux/scripts/container-status.shtmux/tmux.conf.tmplzellij/config.kdl.tmplzellij/layouts/container.kdl