Terminal sessions

Table of Contents

1 Summary

  • I have been using terminator for some time for the splitting terminal capabilities. Overall very happy with it but came across a much better solution on the long term…
  • First, splitting terminal windows is fine, but thinking about it, there are very few instances for which I need to see several terminals \textit{at the same time}! So tabs are nice for this reason, because they save space, you just switch between them.
  • Second, I had been using screen once in a while to stay logged in or launch an interactive process on some cluster machine and come back to it later without the worry that there is an electricity shutdown in the building or that my computer decides to stop.
  • With the new setup presented here, I have tabs within sessions I can log back into, which is perfect. That's thanks to byobu together with the tmux back-end. Splits are also possible by the way, but I end up not using them anymore (useless and occupy unnecessary space).
  • Typical use-case: launch several (classic) terminal windows (possibly across different workspaces), launch byobu/tmux within each, create sessions and tabs and switch between them across these several terminal windows. From home, log to the work machine, launch byobu/tmux to find the exact same sessions/tabs! There are some interesting plugins too, for instance to save a session that can "survive" a reboot.

2 Byobu

  • Used for management of sessions/tabs.
  • One problem: when sessions exist and then quit, and log back into, this creates duplicate sessions. Solution: use tmux to log back into (not byobu). With the simple script below place in \textit{/usr/bin} this takes care of the issue:
#!/bin/bash

if byobu list-sessions #sessions exist
then
	/usr/bin/tmux attach #log into
else
	/usr/bin/byobu #launch byobu
fi

3 Shortcuts

  • Prefix: CTRL+B
  • F6: quit session (can log back into)
  • F2: new window, F8 rename
  • CTRL+SHIFT+F2: new session, CTRL+F8: rename
  • ALT+arrows: navigate
  • SHIFT+F1: help

4 Tmux

  • Awesome appearance with the tmux-config plugin: https://github.com/samoshkin/tmux-config
  • To copy/paste: normal procedure too complicated (scroll back mode, enter then space…), I just do the normal select + middle button but with shift pressed (which overrides the mouse mode).
  • Another useful plugin is tmux-resurrect, which enables saving/restoring sessions even when the computer has been shutdown/rebooted.
  • tmux.conf mostly from tmux-config:
# ==========================
# ===  General settings  ===
# ==========================

set -g default-terminal "screen-256color"
set -g history-limit 20000
set -g buffer-limit 20
set -sg escape-time 0
set -g display-time 1500
set -g remain-on-exit off
set -g repeat-time 300
setw -g allow-rename off
setw -g automatic-rename off
setw -g aggressive-resize off

# Change prefix key to C-a, easier to type, same to "screen"
#unbind C-b
#set -g prefix C-a
#unbind-key -n C-b
set -g prefix ^B
set -g prefix2 ^B
bind b send-prefix
#C-b : prefix! Then use C-b C-s/r to save/restore

# Set parent terminal title to reflect current window in tmux session 
set -g set-titles on
set -g set-titles-string "#I:#W"

# Start index of window/pane with 1, because we're humans, not computers
set -g base-index 1
setw -g pane-base-index 1

# Enable mouse support
set -g mouse on

# To copy, drag to highlight text in yellow, press Enter and then release mouse
# Use vim keybindings in copy mode
setw -g mode-keys vi
# Update default binding of `Enter` to also use copy-pipe
#unbind -t vi-copy Enter
#bind-key -t vi-copy Enter copy-pipe "pbcopy"

# ==========================
# ===   Key bindings     ===
# ==========================

# Unbind default key bindings, we're going to override
unbind "\$" # rename-session
unbind ,    # rename-window
unbind %    # split-window -h
unbind '"'  # split-window
unbind }    # swap-pane -D
unbind {    # swap-pane -U
unbind [    # paste-buffer
unbind ]    
unbind "'"  # select-window
unbind n    # next-window
unbind p    # previous-window
unbind l    # last-window
unbind M-n  # next window with alert
unbind M-p  # next window with alert
unbind o    # focus thru panes
unbind &    # kill-window
unbind "#"  # list-buffer 
unbind =    # choose-buffer
unbind z    # zoom-pane
unbind M-Up  # resize 5 rows up
unbind M-Down # resize 5 rows down
unbind M-Right # resize 5 rows right
unbind M-Left # resize 5 rows left


# Edit configuration and reload
bind C-e new-window -n 'tmux.conf' "sh -c '\${EDITOR:-vim} ~/.tmux.conf && tmux source ~/.tmux.conf && tmux display \"Config reloaded\"'"

# Reload tmux configuration 
bind C-r source-file ~/.tmux.conf \; display "Config reloaded"

# new window and retain cwd
bind c new-window -c "#{pane_current_path}"

# Prompt to rename window right after it's created
#set-hook -g after-new-window 'command-prompt -I "#{window_name}" "rename-window '%%'"'
#set-hook -g after-new-window 'command-prompt -I "" "rename-window '%%'"'
#set-hook -g after-new-session 'command-prompt -I "" "rename-session '%%'"'

# Rename session and window
#bind r command-prompt -I "#{window_name}" "rename-window '%%'"
#bind R command-prompt -I "#{session_name}" "rename-session '%%'"

# Split panes
bind | split-window -h -c "#{pane_current_path}"
bind _ split-window -v -c "#{pane_current_path}"

# Select pane and windows
bind -r C-[ previous-window
bind -r C-] next-window
bind -r [ select-pane -t :.-
bind -r ] select-pane -t :.+
bind -r Tab last-window   # cycle thru MRU tabs
bind -r C-o swap-pane -D

# Zoom pane
bind + resize-pane -Z

# Link window
bind L command-prompt -p "Link window from (session:window): " "link-window -s %% -a"

# Swap panes back and forth with 1st pane
# When in main-(horizontal|vertical) layouts, the biggest/widest panel is always @1
bind \ if '[ #{pane_index} -eq 1 ]' \
     'swap-pane -s "!"' \
     'select-pane -t:.1 ; swap-pane -d -t 1 -s "!"'

# Kill pane/window/session shortcuts
bind x kill-pane
bind X kill-window
bind C-x confirm-before -p "kill other windows? (y/n)" "kill-window -a"
bind Q confirm-before -p "kill-session #S? (y/n)" kill-session

# Merge session with another one (e.g. move all windows)
# If you use adhoc 1-window sessions, and you want to preserve session upon exit
# but don't want to create a lot of small unnamed 1-window sessions around
# move all windows from current session to main named one (dev, work, etc)
bind C-u command-prompt -p "Session to merge with: " \
   "run-shell 'yes | head -n #{session_windows} | xargs -I {} -n 1 tmux movew -t %%'"

# Detach from session
bind d detach
bind D if -F '#{session_many_attached}' \
    'confirm-before -p "Detach other clients? (y/n)" "detach -a"' \
    'display "Session has only 1 client attached"'

# Hide status bar on demand
bind C-s if -F '#{s/off//:status}' 'set status off' 'set status on'



# ==================================================
# === Window monitoring for activity and silence ===
# ==================================================
bind m setw monitor-activity \; display-message 'Monitor window activity [#{?monitor-activity,ON,OFF}]'
bind M if -F '#{monitor-silence}' \
    'setw monitor-silence 0 ; display-message "Monitor window silence [OFF]"' \
    'command-prompt -p "Monitor silence: interval (s)" "setw monitor-silence %%"'

# Activity bell and whistles
set -g visual-activity on

# TODO: Does not work as well, check on newer versions
# set -g visual-silence on

# BUG: bell-action other ignored · Issue #1027 · tmux/tmux · GitHub - https://github.com/tmux/tmux/issues/1027
# set -g visual-bell on
# setw -g bell-action other

# ================================================
# ===     Copy mode, scroll and clipboard      ===
# ================================================
set -g @copy_use_osc52_fallback on

# Prefer vi style key table
setw -g mode-keys vi

bind p paste-buffer
bind C-p choose-buffer

# trigger copy mode by
#bind -n M-Up copy-mode

# Scroll up/down by 1 line, half screen, whole screen
bind -T copy-mode-vi M-Up              send-keys -X scroll-up
bind -T copy-mode-vi M-Down            send-keys -X scroll-down
bind -T copy-mode-vi M-PageUp          send-keys -X halfpage-up
bind -T copy-mode-vi M-PageDown        send-keys -X halfpage-down
bind -T copy-mode-vi PageDown          send-keys -X page-down
bind -T copy-mode-vi PageUp            send-keys -X page-up

# When scrolling with mouse wheel, reduce number of scrolled rows per tick to "2" (default is 5)
bind -T copy-mode-vi WheelUpPane       select-pane \; send-keys -X -N 2 scroll-up
bind -T copy-mode-vi WheelDownPane     select-pane \; send-keys -X -N 2 scroll-down

# wrap default shell in reattach-to-user-namespace if available
# there is some hack with `exec & reattach`, credits to "https://github.com/gpakosz/.tmux"
# don't really understand how it works, but at least window are not renamed to "reattach-to-user-namespace"
if -b "command -v reattach-to-user-namespace > /dev/null 2>&1" \
    "run 'tmux set -g default-command \"exec $(tmux show -gv default-shell) 2>/dev/null & reattach-to-user-namespace -l $(tmux show -gv default-shell)\"'"

yank="~/.tmux/yank.sh"

# Copy selected text
bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "$yank"
bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "$yank"
bind -T copy-mode-vi Y send-keys -X copy-line \;\
    run "tmux save-buffer - | $yank"
bind-key -T copy-mode-vi D send-keys -X copy-end-of-line \;\
    run "tmux save-buffer - | $yank"
bind -T copy-mode-vi C-j send-keys -X copy-pipe-and-cancel "$yank"
bind-key -T copy-mode-vi A send-keys -X append-selection-and-cancel \;\
    run "tmux save-buffer - | $yank"

# Copy selection on drag end event, but do not cancel copy mode and do not clear selection
# clear select on subsequence mouse click
bind -T copy-mode-vi MouseDragEnd1Pane \
    send-keys -X copy-pipe "$yank"
bind -T copy-mode-vi MouseDown1Pane select-pane \;\
   send-keys -X clear-selection

# iTerm2 works with clipboard out of the box, set-clipboard already set to "external"
# tmux show-options -g -s set-clipboard
# set-clipboard on|external

# =====================================
# ===           Theme               ===
# =====================================

# Feel free to NOT use this variables at all (remove, rename)
# this are named colors, just for convenience
color_orange="colour166" # 208, 166
color_purple="colour134" # 135, 134
color_green="colour076" # 070
color_blue="colour39"
color_yellow="colour220"
color_red="colour160"
color_black="colour232"
color_white="white" # 015

# This is a theme CONTRACT, you are required to define variables below
# Change values, but not remove/rename variables itself
color_dark="$color_black"
color_light="$color_white"
color_session_text="$color_blue"
color_status_text="colour245"
color_main="$color_orange"
color_secondary="$color_purple"
color_level_ok="$color_green"
color_level_warn="$color_yellow"
color_level_stress="$color_red"
color_window_off_indicator="colour088"
color_window_off_status_bg="colour238"
color_window_off_status_current_bg="colour254"

# =====================================
# ===    Appearence and status bar  ===
# ======================================

set -g mode-style "fg=default,bg=$color_main"

# command line style
set -g message-style "fg=$color_main,bg=$color_dark"

# status line style
set -g status-style "fg=$color_status_text,bg=$color_dark"

# window segments in status line
set -g window-status-separator ""
separator_powerline_left=""
separator_powerline_right=""
separator_powerline_left="<"
separator_powerline_right=">"

# setw -g window-status-style "fg=$color_status_text,bg=$color_dark"
setw -g window-status-format " #I:#W "
setw -g window-status-current-style "fg=$color_light,bold,bg=$color_main"
setw -g window-status-current-format "#[fg=$color_dark,bg=$color_main]$separator_powerline_right#[default] #I:#W# #[fg=$color_main,bg=$color_dark]$separator_powerline_right#[default]"

# when window has monitoring notification
setw -g window-status-activity-style "fg=$color_main"

# outline for active pane
setw -g pane-active-border-style "fg=$color_main"

# general status bar settings
set -g status on
set -g status-interval 5
set -g status-position top
set -g status-justify left
set -g status-right-length 100

# define widgets we're going to use in status bar
# note, that this is not the complete list, some of them are loaded from plugins
wg_session="#[fg=$color_session_text] #S #[default]"
wg_battery="#{battery_status_fg} #{battery_icon} #{battery_percentage}"
wg_date="#[fg=$color_secondary]%h %d %H:%M#[default]"
wg_user_host="#[fg=$color_secondary]#(whoami)#[default]@#H"
wg_is_zoomed="#[fg=$color_dark,bg=$color_secondary]#{?window_zoomed_flag,[Z],}#[default]"
# TODO: highlighted for nested local session as well
wg_is_keys_off="#[fg=$color_light,bg=$color_window_off_indicator]#([ $(tmux show-option -qv key-table) = 'off' ] && echo 'OFF')#[default]"

set -g status-left "$wg_session"
set -g status-right "#{prefix_highlight} $wg_is_keys_off $wg_is_zoomed #{sysstat_cpu} | #{sysstat_mem} | #{sysstat_loadavg} | $wg_user_host | $wg_date $wg_battery #{online_status}"

# online and offline icon for tmux-online-status
set -g @online_icon "#[fg=$color_level_ok]●#[default]"
set -g @offline_icon "#[fg=$color_level_stress]●#[default]"

# Configure view templates for tmux-plugin-sysstat "MEM" and "CPU" widget
set -g @sysstat_mem_view_tmpl 'MEM:#[fg=#{mem.color}]#{mem.pused}#[default] #{mem.used}'

# Configure colors for tmux-plugin-sysstat "MEM" and "CPU" widget
set -g @sysstat_cpu_color_low "$color_level_ok"
set -g @sysstat_cpu_color_medium "$color_level_warn"
set -g @sysstat_cpu_color_stress "$color_level_stress"

set -g @sysstat_mem_color_low "$color_level_ok"
set -g @sysstat_mem_color_medium "$color_level_warn"
set -g @sysstat_mem_color_stress "$color_level_stress"

set -g @sysstat_swap_color_low "$color_level_ok"
set -g @sysstat_swap_color_medium "$color_level_warn"
set -g @sysstat_swap_color_stress "$color_level_stress"


# Configure tmux-battery widget colors
set -g @batt_color_full_charge "#[fg=$color_level_ok]"
set -g @batt_color_high_charge "#[fg=$color_level_ok]"
set -g @batt_color_medium_charge "#[fg=$color_level_warn]"
set -g @batt_color_low_charge "#[fg=$color_level_stress]"

# Configure tmux-prefix-highlight colors
set -g @prefix_highlight_output_prefix '['
set -g @prefix_highlight_output_suffix ']'
set -g @prefix_highlight_fg "$color_dark"
set -g @prefix_highlight_bg "$color_secondary"
set -g @prefix_highlight_show_copy_mode 'on'
set -g @prefix_highlight_copy_mode_attr "fg=$color_dark,bg=$color_secondary"


# =====================================
# ===        Renew environment      ===
# =====================================
#set -g update-environment \
#  "DISPLAY\
#  SSH_ASKPASS\
#  SSH_AUTH_SOCK\
#  SSH_AGENT_PID\
#  SSH_CONNECTION\
#  SSH_TTY\
#  WINDOWID\
#  XAUTHORITY"
set -g update-environment \
  "DISPLAY\
  WINDOWID\
  XAUTHORITY"

bind '$' run "~/.tmux/renew_env.sh"


# ============================
# ===       Plugins        ===
# ============================
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-battery'
set -g @plugin 'tmux-plugins/tmux-prefix-highlight'
set -g @plugin 'tmux-plugins/tmux-online-status'
set -g @plugin 'tmux-plugins/tmux-sidebar'
set -g @plugin 'tmux-plugins/tmux-copycat'
set -g @plugin 'tmux-plugins/tmux-open'
set -g @plugin 'samoshkin/tmux-plugin-sysstat'
set -g @plugin 'tmux-plugins/tmux-resurrect'

set -g @resurrect-save 'S'
set -g @resurrect-restore 'R'

# Plugin properties
set -g @sidebar-tree 't'
set -g @sidebar-tree-focus 'T'
set -g @sidebar-tree-command 'tree -C'

set -g @open-S 'https://www.google.com/search?q='


# ==============================================
# ===   Nesting local and remote sessions     ===
# ==============================================

# Session is considered to be remote when we ssh into host
#if-shell 'test -n "$SSH_CLIENT"' \
#    'source-file ~/.tmux/tmux.remote.conf'

# We want to have single prefix key "C-a", usable both for local and remote session
# we don't want to "C-a" + "a" approach either
# Idea is to turn off all key bindings and prefix handling on local session,
# so that all keystrokes are passed to inner/remote session

# see: toggle on/off all keybindings · Issue #237 · tmux/tmux - https://github.com/tmux/tmux/issues/237

# Also, change some visual styles when window keys are off
bind -T root F12  \
    set prefix None \;\
    set key-table off \;\
    set status-style "fg=$color_status_text,bg=$color_window_off_status_bg" \;\
    set window-status-current-format "#[fg=$color_window_off_status_bg,bg=$color_window_off_status_current_bg]$separator_powerline_right#[default] #I:#W# #[fg=$color_window_off_status_current_bg,bg=$color_window_off_status_bg]$separator_powerline_right#[default]" \;\
    set window-status-current-style "fg=$color_dark,bold,bg=$color_window_off_status_current_bg" \;\
    if -F '#{pane_in_mode}' 'send-keys -X cancel' \;\
    refresh-client -S \;\

bind -T off F12 \
  set -u prefix \;\
  set -u key-table \;\
  set -u status-style \;\
  set -u window-status-current-style \;\
  set -u window-status-current-format \;\
  refresh-client -S

# Run all plugins' scripts
run '~/.tmux/plugins/tpm/tpm'

  • if you have some issues with X forwarding, even on a localhost, you can try the following in your .bashrc file
# -- Improved X11 forwarding through GNU Screen (or tmux).
# If not in screen or tmux, update the DISPLAY cache.
# If we are, update the value of DISPLAY to be that in the cache.
function update-x11-forwarding
{
    if [ -z "$STY" -a -z "$TMUX" ]; then
	echo $DISPLAY > ~/.display.txt
    else
	export DISPLAY=`cat ~/.display.txt`
    fi
}

# This is run before every command.
preexec() {
    # Don't cause a preexec for PROMPT_COMMAND.
    # Beware!  This fails i]52;;Zg==OMMAND is a string containing more than one command.
    [ "$BASH_COMMAND" = "$PROMPT_COMMAND" ] && return

    update-x11-forwarding

    # Debugging.
    #echo DISPLAY = $DISPLAY, display.txt = `cat ~/.display.txt`, STY = $STY, TMUX = $TMUX
}
trap 'preexec' DEBUG

5 Scroll-down terminals

  • In addition to byobu+tmux I also use guake or tilix which are nice scroll-down terminals to type a quick command.
  • Some people use byobu inside guake, which is nice when the latter is the main way to access the terminal, which is not my case.

6 Screenshot

screenshot.png

Author: Vianney Lebouteiller

Created: 2019-05-05 Sun 01:43

Validate