The Terminal Programmer

Copying to clipboard from tmux and vim using OSC 52

Suraj N. Kurapati

(solution, tmux, vim, terminal, clipboard)

I switched to an Acer C720 chromebook as my primary computer recently. I made myself at home on its Gentoo Linux based Google Chrome OS by running a Crouton powered Debian Linux chroot.

However, the lack of X11 clipboard integration in its X11-based Aura desktop environment posed a significant problem for me because, until now, I used X11 forwarding to run the xsel(1) program as a way of copying and pasting text between my system clipboard and programs running under tmux(1) in remote SSH sessions. Now I had to find a different solution.

In my search, I came upon the OSC 52 escape sequence, which causes the receiving terminal to copy text into the system clipboard. However, the current stable tmux(1) version 1.9a only allows 180 bytes of text to be copied in this way, so I reported this issue to the tmux-users mailing list where developer Nicholas Marriott quickly provided a patch to fix it.

Armed with a patched tmux(1), I distilled my working example of the OSC 52 escape sequence into the yank(1) shell script and integrated it into my tmux and vim configuration. Now I can readily copy text from tmux and vim running in any terminal session, whether local or remote or even nested therein, all without X11 forwarding and having to keep environment variables like $DISPLAY and $XAUTHORITY up to date!

Finally, to paste text from the system clipboard into a terminal session, I simply use the Control-Shift-V shortcut of the Secure Shell terminal emulator. Thus, what began as a problem led to the discovery of a superior solution! :-)

The new way: OSC 52

In ~/.tmux.conf file:

# transfer copied text to attached terminal with yank:
# https://github.com/sunaku/home/blob/master/bin/yank
bind-key -t vi-copy y copy-pipe 'yank > #{pane_tty}'

# transfer copied text to attached terminal with yank:
# https://github.com/sunaku/home/blob/master/bin/yank
bind-key -n M-y run-shell 'tmux save-buffer - | yank > #{pane_tty}'

# transfer previously copied text (chosen from a menu) to attached terminal:
# https://github.com/sunaku/home/blob/master/bin/yank
bind-key -n M-Y choose-buffer 'run-shell "tmux save-buffer -b \"%%\" - | yank > #{pane_tty}"'

In ~/.vimrc file:

" copy the current text selection to the system clipboard
if has('gui_running')
  noremap <Leader>y "+y
else
  " copy to attached terminal using the yank(1) script:
  " https://github.com/sunaku/home/blob/master/bin/yank
  noremap <silent> <Leader>y y
        \ :silent execute
        \   '!/bin/echo -n' shellescape(escape(@0, '\'), 1) '<Bar> yank'
        \ <Bar>redraw!<Return>
endif

In ~/bin/yank file, which is also available on GitHub:

#!/bin/sh
#
# Usage: yank [FILE...]
#
# Copies the contents of the given files (or stdin if no files are given) to
# the terminal that runs this program.  If this program is run inside tmux(1),
# then it also copies the given contents into tmux's current clipboard buffer.
# If this program is run inside X11, then it also copies to the X11 clipboard.
#
# This is achieved by writing an OSC 52 escape sequence to the said terminal.
# The maximum length of an OSC 52 escape sequence is 100_000 bytes, of which
# 7 bytes are occupied by a "\033]52;c;" header, 1 byte by a "\a" footer, and
# 99_992 bytes by the base64-encoded result of 74_994 bytes of copyable text.
#
# In other words, this program can only copy up to 74_994 bytes of its input.
# However, in such cases, this program tries to bypass the input length limit
# by copying directly to the X11 clipboard if a $DISPLAY server is available;
# otherwise, it emits a warning (on stderr) about the number of bytes dropped.
#
# See http://en.wikipedia.org/wiki/Base64 for the 4*ceil(n/3) length formula.
# See http://sourceforge.net/p/tmux/mailman/message/32221257 for copy limits.
# See http://sourceforge.net/p/tmux/tmux-code/ci/a0295b4c2f6 for DCS in tmux.
#
# Written in 2014 by Suraj N. Kurapati and documented at http://goo.gl/NwYqfW

buf=$( cat "$@" ) len=$( echo -n "$buf" | wc -c ) max=74994
osc52="\033]52;c;$( echo -n "$buf" | head -c $max | base64 | tr -d '\r\n' )\a"
[ -n "$TMUX" ] && osc52="\033Ptmux;\033$osc52\033\\" && tmux set-buffer "$buf"
printf "$osc52"

# try bypassing the OSC 52 length limit by copying directly to X11 clipboard
[ $len -le $max ] || echo -n "$buf" | { xsel -i -b || xclip -sel c ;} ||
echo "$0: input too long: $len bytes; dropped $(( len-max )) bytes at end" >&2

The old way: X11 forwarding

In ~/.tmux.conf file:

# transfer copied text to X primary selection
bind-key -n M-y run-shell 'tmux save-buffer - | xsel -p -i'

# transfer copied text to X clipboard selection
bind-key -n M-Y run-shell 'tmux save-buffer - | xsel -b -i'

# paste X primary selection
bind-key -n M-p run-shell 'xsel -p -o | tmux load-buffer - \; paste-buffer \; delete-buffer'

# paste X clipboard selection
bind-key -n M-P run-shell 'xsel -b -o | tmux load-buffer - \; paste-buffer \; delete-buffer'

In ~/.vimrc file:

" copy to primary selection
noremap <Leader>y "*y

" copy to clipboard selection
noremap <Leader>Y "+y

" paste from primary selection
noremap <Leader>p "*p

" paste from clipboard selection
noremap <Leader>P "+p

Updates