Last active
July 30, 2026 15:33
-
-
Save HackingGate/ebc5bd4d474d6aaffa05bfb84cc1e60f to your computer and use it in GitHub Desktop.
Emacs-friendly tmux setup with clipboard copy, auto light/dark mode, and longer message display time
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 tmux and shell environment setup..." | |
| # Detect OS/session and configure clipboard behavior | |
| clip_cmd="" | |
| copy_target="tmux 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 | |
| # For SSH sessions, prefer tmux's OSC 52 clipboard path so copied text lands on | |
| # the local machine connected to the SSH client instead of the remote host. | |
| use_tmux_clipboard=1 | |
| if [[ "${is_ssh}" -eq 0 ]] && [[ "${has_display}" -eq 1 ]]; then | |
| use_tmux_clipboard=0 | |
| fi | |
| case "${os_name}" in | |
| Linux) | |
| if [[ "${use_tmux_clipboard}" -eq 0 ]]; then | |
| if [[ "${is_wayland}" -eq 1 ]]; then | |
| if ! command -v wl-copy >/dev/null 2>&1; then | |
| if command -v apt >/dev/null 2>&1; then | |
| sudo apt update >/dev/null 2>&1 | |
| sudo apt install -y wl-clipboard >/dev/null 2>&1 | |
| elif command -v dnf >/dev/null 2>&1; then | |
| sudo dnf install -y wl-clipboard >/dev/null 2>&1 | |
| fi | |
| fi | |
| if command -v wl-copy >/dev/null 2>&1; then | |
| clip_cmd="wl-copy" | |
| fi | |
| else | |
| if ! command -v xclip >/dev/null 2>&1; then | |
| if command -v apt >/dev/null 2>&1; then | |
| sudo apt update >/dev/null 2>&1 | |
| sudo apt install -y xclip >/dev/null 2>&1 | |
| elif command -v dnf >/dev/null 2>&1; then | |
| sudo dnf install -y xclip >/dev/null 2>&1 | |
| fi | |
| fi | |
| if command -v xclip >/dev/null 2>&1; then | |
| clip_cmd="xclip -in -selection clipboard" | |
| fi | |
| fi | |
| fi | |
| ;; | |
| Darwin) | |
| if [[ "${use_tmux_clipboard}" -eq 0 ]]; then | |
| clip_cmd="pbcopy" | |
| fi | |
| ;; | |
| esac | |
| # Define the final copy action | |
| if [[ -n "${clip_cmd}" ]]; then | |
| copy_action="copy-pipe-and-cancel \"${clip_cmd}\"" | |
| copy_target="local desktop clipboard helper (${clip_cmd})" | |
| else | |
| copy_action="copy-selection-and-cancel" | |
| fi | |
| # Version-gated features. An older tmux treats an unknown terminal feature or | |
| # option value as an error and aborts the rest of the config, so these lines are | |
| # emitted only when the running tmux understands them. | |
| # | |
| # hyperlinks feature -> tmux 3.4+ | |
| # extkeys feature and "extended-keys always" -> tmux 3.2+ | |
| hyperlinks_line="# OSC 8 hyperlinks need tmux 3.4+; this tmux is older, feature omitted" | |
| extkeys_lines="# Extended keys need tmux 3.2+; this tmux is older, feature omitted" | |
| tmux_major="" | |
| tmux_minor="" | |
| if command -v tmux >/dev/null 2>&1; then | |
| tmux_ver="$(tmux -V 2>/dev/null | awk '{print $2}')" | |
| tmux_major="${tmux_ver%%.*}" | |
| tmux_minor="${tmux_ver#*.}" | |
| tmux_minor="${tmux_minor%%[!0-9]*}" | |
| if [[ ! "${tmux_major}" =~ ^[0-9]+$ ]] || [[ ! "${tmux_minor}" =~ ^[0-9]+$ ]]; then | |
| tmux_major="" | |
| tmux_minor="" | |
| fi | |
| fi | |
| if [[ -n "${tmux_major}" ]]; then | |
| if (( tmux_major > 3 || (tmux_major == 3 && tmux_minor >= 4) )); then | |
| hyperlinks_line="# OSC 8 hyperlink passthrough (tmux 3.4+); without it tmux strips the | |
| # escape and clickable link text renders as plain text | |
| set -as terminal-features ',*:hyperlinks'" | |
| fi | |
| if (( tmux_major > 3 || (tmux_major == 3 && tmux_minor >= 2) )); then | |
| extkeys_lines="# Extended keys (tmux 3.2+): lets an app inside the pane tell Shift+Enter | |
| # apart from Enter, Ctrl+Enter apart from Ctrl+J, and so on. | |
| # | |
| # 'always' rather than 'on' is deliberate. With 'on', tmux forwards extended | |
| # keys only to a pane that asks for them first; several TUIs -- Claude Code | |
| # among them -- never send that request, so Shift+Enter silently arrives as | |
| # plain Enter and submits instead of inserting a newline. | |
| # | |
| # The default extended-keys-format is xterm (CSI 27~), which Emacs 27+ and | |
| # recent Vim decode natively. Switch to extended-keys-format csi-u only if an | |
| # app you use wants the kitty-style CSI u encoding instead. | |
| # | |
| # This cannot rescue a terminal that never sends the distinct key in the first | |
| # place: VTE terminals (Ptyxis, GNOME Terminal, Tilix, Terminator, Guake) have | |
| # no released kitty-keyboard-protocol support, and JetBrains IDE terminals do | |
| # not either. There, Shift+Enter stays indistinguishable from Enter no matter | |
| # what tmux does -- use Ctrl+J for a newline. Works: kitty, Ghostty, WezTerm, | |
| # iTerm2, Alacritty, foot, Windows Terminal. | |
| set -s extended-keys always | |
| set -as terminal-features ',xterm*:extkeys'" | |
| fi | |
| fi | |
| # Create tmux configuration | |
| cat > "${HOME}/.tmux.conf" <<EOF | |
| # --- 1. Colors & Terminal --- | |
| set -g default-terminal "screen-256color" | |
| set -as terminal-overrides ',xterm*:Tc:smcup@:rmcup@' | |
| set -as terminal-features ',xterm*:clipboard' | |
| ${hyperlinks_line} | |
| ${extkeys_lines} | |
| set -s set-clipboard on | |
| set -g allow-passthrough on | |
| set -g focus-events on | |
| set -ag update-environment "DISPLAY WAYLAND_DISPLAY XAUTHORITY SSH_AUTH_SOCK SSH_CONNECTION SSH_TTY XDG_SESSION_TYPE" | |
| # --- 2. Visibility & History --- | |
| set -g mouse on | |
| set -g history-limit 1000000 | |
| # High-visibility highlight (Bright Cyan) | |
| set -g mode-style "bg=brightcyan,fg=black,bold" | |
| # --- 3. Keybindings --- | |
| set -g mode-keys emacs | |
| setw -g mode-keys emacs | |
| set -g status-keys emacs | |
| # --- 4. System Clipboard & Mouse Selection --- | |
| bind -n MouseDrag1Pane if-shell -F "#{mouse_any_flag}" "send-keys -M" "copy-mode -M" | |
| bind -T copy-mode-emacs MouseDragEnd1Pane send-keys -X ${copy_action} | |
| bind -T copy-mode-vi MouseDragEnd1Pane send-keys -X ${copy_action} | |
| # Copy keys | |
| bind -T copy-mode-emacs Enter send-keys -X ${copy_action} | |
| bind -T copy-mode-emacs M-w send-keys -X ${copy_action} | |
| bind -T copy-mode-emacs C-w send-keys -X ${copy_action} | |
| bind -T copy-mode-vi Enter send-keys -X ${copy_action} | |
| # Selection starting keys | |
| unbind -T copy-mode-emacs C-@ | |
| bind -T copy-mode-emacs C-@ send-keys -X begin-selection | |
| # Optional: double-click word, triple-click line | |
| bind -T copy-mode-emacs DoubleClick1Pane select-pane \; send-keys -X select-word \; send-keys -X ${copy_action} | |
| bind -T copy-mode-emacs TripleClick1Pane select-pane \; send-keys -X select-line \; send-keys -X ${copy_action} | |
| bind -T copy-mode-vi DoubleClick1Pane select-pane \; send-keys -X select-word \; send-keys -X ${copy_action} | |
| bind -T copy-mode-vi TripleClick1Pane select-pane \; send-keys -X select-line \; send-keys -X ${copy_action} | |
| # --- 5. Quality of Life --- | |
| set -g base-index 1 | |
| setw -g pane-base-index 1 | |
| set -g display-time 4000 | |
| bind r source-file ~/.tmux.conf \; display-message "Tmux config reloaded!" | |
| EOF | |
| # --- Force apply tmux changes to ALL sessions --- | |
| if command -v tmux >/dev/null 2>&1 && tmux has-session >/dev/null 2>&1; then | |
| tmux list-sessions -F '#S' > /tmp/tmux_sessions.txt | |
| while read -r s; do | |
| tmux set-option -t "$s" -g mode-keys emacs >/dev/null 2>&1 || true | |
| tmux set-window-option -t "$s" -g mode-keys emacs >/dev/null 2>&1 || true | |
| tmux source-file -t "$s" "${HOME}/.tmux.conf" >/dev/null 2>&1 || true | |
| done < /tmp/tmux_sessions.txt | |
| rm /tmp/tmux_sessions.txt | |
| fi | |
| echo "---" | |
| echo "=== HOW TO COPY (Shift+Number Compatible) ===" | |
| echo "" | |
| echo "[Method A: With Mouse / Trackpad]" | |
| echo " • Click & Drag -> Release to auto-copy." | |
| echo "" | |
| echo "[Method B: Keyboard Selection]" | |
| echo " 1. Enter Copy Mode: Ctrl+b [" | |
| echo " 2. Start Selection: Press 'Ctrl+@'" | |
| echo " 3. Copy & Exit: Press 'Enter' (or 'Alt+w' / 'Ctrl+w')" | |
| echo "" | |
| echo "[Method C: Bypass Tmux]" | |
| echo " • Hold SHIFT while dragging with the mouse." | |
| echo "===================" | |
| echo "Reload all sessions: Press Ctrl+b, then 'r'" | |
| echo "" | |
| echo "=== CLICKABLE LINKS (OSC 8) ===" | |
| echo " • OPEN A LINK WITH CTRL+CLICK. With 'mouse on', tmux takes plain" | |
| echo " clicks and Shift+click; only Ctrl reaches the terminal. A link that" | |
| echo " looks like plain text is usually the wrong modifier, not a broken" | |
| echo " config -- try Ctrl+click before changing anything." | |
| echo " • tmux reads terminal-features when a client ATTACHES, so sourcing the" | |
| echo " config is not enough: detach and reattach ('tmux detach', then" | |
| echo " 'tmux attach') before hyperlinks work." | |
| echo " • Some CLIs refuse to emit OSC 8 when TERM is screen-*, and" | |
| echo " FORCE_HYPERLINK=1 overrides that -- but do NOT set it globally." | |
| echo " Inside a full-screen app that grabs the mouse (check with" | |
| echo " 'tmux display -p \"#{mouse_any_flag}\"'), clicks go to the app, so an" | |
| echo " OSC 8 link cannot be activated at all -- while a PLAIN-TEXT URL" | |
| echo " stays Ctrl+clickable via the terminal's own URL matching. Forcing" | |
| echo " hyperlinks there turns working URLs into dead ones." | |
| echo " • Verify the whole chain without involving any CLI:" | |
| echo " printf '\\e]8;;https://example.com\\e\\\\LINK\\e]8;;\\e\\\\\\n'" | |
| echo " Run it in a PLAIN pane. Piping it through a tool that captures and" | |
| echo " re-renders output proves nothing -- escapes never reach the tty." | |
| echo " • Your terminal emulator must render OSC 8: Ptyxis, GNOME Terminal and" | |
| echo " other VTE terminals do, as do kitty, WezTerm, Ghostty, iTerm2 and" | |
| echo " Windows Terminal. Plain URLs stay clickable either way." | |
| echo "" | |
| echo "=== TUI CODING AGENTS (Claude Code, Codex, and friends) ===" | |
| echo " • SHIFT+ENTER for a newline needs 'extended-keys always', set above." | |
| echo " 'on' is not enough: tmux then waits for the pane to request extended" | |
| echo " keys and these apps never do. Applies on source-file, but the" | |
| echo " matching extkeys terminal-feature is read at ATTACH time -- detach and" | |
| echo " reattach. On a VTE terminal (Ptyxis, GNOME Terminal) the key never" | |
| echo " leaves the emulator, so use Ctrl+J instead; no tmux setting fixes it." | |
| echo " • DESKTOP NOTIFICATIONS and progress-bar escapes reach the outer" | |
| echo " terminal through 'allow-passthrough on', set above." | |
| echo " • OSC 8 LINKS printed by these apps are usually NOT clickable, and this" | |
| echo " config is not the reason. They hold the mouse (mouse_any_flag=1) and" | |
| echo " re-render output through their own layer, so the escape either never" | |
| echo " reaches the tty or the click never reaches the terminal. Plain-text" | |
| echo " URLs stay Ctrl+clickable via the terminal's own URL matching, which" | |
| echo " is why links look fine until you try a labelled one." | |
| echo " • PASTING AN IMAGE from the clipboard does not work through tmux: tmux" | |
| echo " forwards text only. Save the image to a file and pass the path." |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
bash <(curl -fsSL https://gist.githubusercontent.com/HackingGate/ebc5bd4d474d6aaffa05bfb84cc1e60f/raw/tmux.sh)Clipboard copy, automatic light/dark theme switching, and a longer tmux message display time are included.