Last active
October 2, 2026 16:39
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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." |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
curl -fsSL https://gist.githubusercontent.com/HackingGate/9c46e8ee00fac6babfccab224f650f7e/raw/zellij.sh | bash