Skip to content

Instantly share code, notes, and snippets.

@HackingGate
Last active October 2, 2026 16:39
Show Gist options
  • Select an option

  • Save HackingGate/9c46e8ee00fac6babfccab224f650f7e to your computer and use it in GitHub Desktop.

Select an option

Save HackingGate/9c46e8ee00fac6babfccab224f650f7e to your computer and use it in GitHub Desktop.
zellij setup: clipboard and scrollback options, lock toggle moved from Ctrl g to Ctrl b, other stock keybindings kept
#!/usr/bin/env bash
set -euo pipefail
echo "Starting zellij setup..."
# Detect OS/session and configure clipboard behavior
clip_cmd=""
copy_target="zellij OSC 52 host clipboard"
os_name="$(uname -s 2>/dev/null || true)"
is_wayland=0
is_ssh=0
has_display=0
if [[ "${XDG_SESSION_TYPE:-}" == "wayland" ]] || [[ -n "${WAYLAND_DISPLAY:-}" ]]; then
is_wayland=1
fi
if [[ -n "${SSH_CONNECTION:-}" ]] || [[ -n "${SSH_CLIENT:-}" ]]; then
is_ssh=1
fi
if [[ -n "${DISPLAY:-}" ]] || [[ -n "${WAYLAND_DISPLAY:-}" ]]; then
has_display=1
fi
# Zellij copies through OSC 52 by default, and VTE terminals (Ptyxis, GNOME
# Terminal, Tilix) ignore OSC 52, so a mouse selection there copies nothing.
# A local desktop session gets a real clipboard helper instead. Over SSH, OSC 52
# stays: it lands on the machine running the terminal, not the remote host.
use_osc52=1
if [[ "${is_ssh}" -eq 0 ]] && [[ "${has_display}" -eq 1 ]]; then
use_osc52=0
fi
install_pkg() {
if command -v apt >/dev/null 2>&1; then
sudo apt update >/dev/null 2>&1
sudo apt install -y "$1" >/dev/null 2>&1
elif command -v dnf >/dev/null 2>&1; then
sudo dnf install -y "$1" >/dev/null 2>&1
elif command -v pacman >/dev/null 2>&1; then
sudo pacman -S --needed --noconfirm "$1" >/dev/null 2>&1
fi
}
case "${os_name}" in
Linux)
if [[ "${use_osc52}" -eq 0 ]]; then
if [[ "${is_wayland}" -eq 1 ]]; then
command -v wl-copy >/dev/null 2>&1 || install_pkg wl-clipboard || true
if command -v wl-copy >/dev/null 2>&1; then
clip_cmd="wl-copy"
fi
else
command -v xclip >/dev/null 2>&1 || install_pkg xclip || true
if command -v xclip >/dev/null 2>&1; then
clip_cmd="xclip -in -selection clipboard"
fi
fi
fi
;;
Darwin)
if [[ "${use_osc52}" -eq 0 ]]; then
clip_cmd="pbcopy"
fi
;;
esac
config_dir="${ZELLIJ_CONFIG_DIR:-${XDG_CONFIG_HOME:-${HOME}/.config}/zellij}"
config_file="${config_dir}/config.kdl"
mkdir -p "${config_dir}"
if [[ -f "${config_file}" ]]; then
backup="${config_file}.bak.$(date +%Y%m%d-%H%M%S)"
cp "${config_file}" "${backup}"
echo "Previous config saved to ${backup}"
else
# No file means stock keybindings; the options and lock toggle set below
# merge over them
: > "${config_file}"
fi
# Set a top-level option in place: drop any live line for it, then append.
# Every other line in the file is left alone.
set_option() {
local key="$1" value="$2"
sed -i.tmp -E "/^${key}[[:space:]]/d" "${config_file}"
rm -f "${config_file}.tmp"
printf '%s %s\n' "${key}" "${value}" >> "${config_file}"
}
unset_option() {
sed -i.tmp -E "/^$1[[:space:]]/d" "${config_file}"
rm -f "${config_file}.tmp"
}
# Move the lock toggle from Ctrl g to Ctrl b. A full keybinds tree has its Ctrl g
# toggles renamed and its other Ctrl b binds dropped; otherwise a small block
# merges over the defaults.
if grep -q '^keybinds' "${config_file}"; then
sed -i.tmp -E \
-e '/bind "Ctrl b" \{ SwitchToMode "([Nn]ormal|[Ll]ocked)"; \}/!{s/bind "Ctrl b" "/bind "/;/^[[:space:]]*bind "Ctrl b" /d;}' \
-e 's/bind "Ctrl g" (\{ SwitchToMode "([Nn]ormal|[Ll]ocked)"; \})/bind "Ctrl b" \1/' \
"${config_file}"
rm -f "${config_file}.tmp"
else
cat >> "${config_file}" <<'EOF'
keybinds {
unbind "Ctrl g"
locked { bind "Ctrl b" { SwitchToMode "normal"; }; }
shared_except "locked" { bind "Ctrl b" { SwitchToMode "locked"; }; }
}
EOF
fi
if [[ -n "${clip_cmd}" ]]; then
set_option copy_command "\"${clip_cmd}\""
copy_target="local desktop clipboard helper (${clip_cmd})"
else
unset_option copy_command
fi
set_option copy_clipboard '"system"'
set_option copy_on_select true
# Default is 10,000 lines. Zellij keeps scrollback as styled cells in memory,
# so tmux-sized 1,000,000 costs gigabytes across a few busy panes.
set_option scroll_buffer_size 100000
if command -v zellij >/dev/null 2>&1; then
if ! zellij --config "${config_file}" setup --check >/dev/null 2>&1; then
echo "WARNING: zellij rejected ${config_file}. Run: zellij --config ${config_file} setup --check"
fi
fi
echo "---"
echo "Updated ${config_file}"
echo "Copy target: ${copy_target}"
echo "scroll_buffer_size applies to new sessions only; the rest applies on save."
echo ""
echo "=== HOW TO COPY ==="
echo "[Method A: Mouse]"
echo " • Click & drag -> release to auto-copy."
echo ""
echo "[Method B: Keyboard, via your editor]"
echo " • Ctrl+s then e opens the pane's scrollback in \$EDITOR; copy from there."
echo ""
echo "[Method C: Bypass zellij]"
echo " • Hold SHIFT while dragging with the mouse."
@HackingGate

Copy link
Copy Markdown
Author
curl -fsSL https://gist.githubusercontent.com/HackingGate/9c46e8ee00fac6babfccab224f650f7e/raw/zellij.sh | bash

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment