Copying to clipboard from tmux and Vim using OSC 52

Suraj N. Kurapati


  1. Problem
    1. Approach
      1. Solution
        1. OSC 52 escapes (the new way)
          1. Create a ~/bin/yank script
            1. Configure your ~/.tmux.conf
              1. Configure your ~/.vimrc
              2. X11 forwarding (the old way)
                1. Configure your ~/.tmux.conf
                  1. Configure your ~/.vimrc

                Problem

                I switched to an Acer C720 chromebook as my primary computer recently. However, its X11-based Aura desktop environment lacks X11 clipboard integration, which prevents me from using X11 forwarding to run the xsel(1) program as a means of copying and pasting text between my system clipboard and programs running remotely in SSH. So, I had to find a different solution for my copy/paste needs.

                Approach

                In my search, I came across the OSC 52 escape sequence, which tells the receiving terminal to copy some specified text into the user’s system clipboard.

                However, this escape sequence is only partially supported in the current stable version of tmux (version 1.9a as of this writing), which limits the amount of text that can be copied (per my observations) to a meager 180 bytes!

                I reported this issue to the tmux-users mailing list, where developer Nicholas Marriott quickly provided a patch to fix it. Armed with this patch, I distilled my working knowledge into a yank script, integrated with tmux and Vim.

                Now I can easily copy text from tmux and Vim running in any terminal session (whether local, remote, or even nested therein!) without needing 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. And so, what began as a problem led to the discovery of a superior solution! :-)

                Solution

                OSC 52 escapes (the new way)

                Create a ~/bin/yank script

                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.
                #
                # NOTE: You might also need to allow this script to bypass tmux (through the
                # "Ptmux;" escape sequence) by enabling allow-passthrough in your tmux.conf:
                #
                #   set-window-option -g allow-passthrough on
                #
                # 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 <https://github.com/sunaku>
                # Also documented at https://sunaku.github.io/tmux-yank-osc52.html
                
                input=$( cat "$@" )
                input() { printf %s "$input" ;}
                known() { command -v "$1" >/dev/null ;}
                maybe() { known "$1" && input | "$@" ;}
                alive() { known "$1" && "$@" >/dev/null 2>&1 ;}
                
                # copy to tmux
                test -n "$TMUX" && maybe tmux load-buffer -
                
                # copy via X11
                test -n "$DISPLAY" && alive xhost && {
                  maybe xsel -i -b || maybe xclip -sel c
                }
                
                # copy via OSC 52
                printf_escape() {
                  esc=$1
                  test -n "$TMUX" -o -z "${TERM##screen*}" && esc="\033Ptmux;\033$esc\033\\"
                  printf "$esc"
                }
                len=$( input | wc -c ) max=74994
                test $len -gt $max && echo "$0: input is $(( len - max )) bytes too long" >&2
                printf_escape "\033]52;c;$( input | head -c $max | base64 | tr -d '\r\n' )\a"
                

                Configure your ~/.tmux.conf

                NOTE: See also my tmux.conf file template on GitHub for reference.

                In ~/.tmux.conf file (if you are using tmux 3.3 or newer):

                # pass "Ptmux;" escape sequences through to the terminal
                set-window-option -g allow-passthrough on
                

                In ~/.tmux.conf file (if you are using tmux 2.4 or newer):

                # transfer copied text to attached terminal with yank
                bind-key -T copy-mode-vi Y send-keys -X copy-pipe 'yank > #{pane_tty}'
                
                # transfer most-recently copied text to attached terminal with 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
                bind-key -n M-Y choose-buffer 'run-shell "tmux save-buffer -b \"%%%\" - | yank > #{pane_tty}"'
                

                In ~/.tmux.conf file (if you are using tmux 2.3 or older):

                # transfer copied text to attached terminal with yank
                bind-key -t vi-copy y copy-pipe 'yank > #{pane_tty}'
                
                # transfer copied text to attached terminal with 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
                bind-key -n M-Y choose-buffer 'run-shell "tmux save-buffer -b \"%%\" - | yank > #{pane_tty}"'
                

                Configure your ~/.vimrc

                In ~/.vimrc file:

                " copy to attached terminal using the yank(1) script:
                " https://github.com/sunaku/home/blob/master/bin/yank
                function! Yank(text) abort
                  let escape = system('yank', a:text)
                  if v:shell_error
                    echoerr escape
                  else
                    call writefile([escape], '/dev/tty', 'b')
                  endif
                endfunction
                noremap <silent> <Leader>y y:<C-U>call Yank(@0)<CR>
                
                " automatically run yank(1) whenever yanking in Vim
                " (this snippet was contributed by Larry Sanderson)
                function! CopyYank() abort
                  call Yank(join(v:event.regcontents, "\n"))
                endfunction
                autocmd TextYankPost * call CopyYank()
                

                X11 forwarding (the old way)

                The disadvantage of this X11 forwarding solution is that you need to run SSH with the -X or -Y options while also keeping environment variables like $DISPLAY and $XAUTHORITY up to date as you disconnect and reconnect your remote sessions.

                Configure your ~/.tmux.conf

                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'
                

                Configure your ~/.vimrc

                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