Skip to content

Instantly share code, notes, and snippets.

@burke
Last active September 26, 2023 11:04
Show Gist options
  • Save burke/5960455 to your computer and use it in GitHub Desktop.
Save burke/5960455 to your computer and use it in GitHub Desktop.
This sets up keybindings in tmux that allow you to copy/paste to/from your OS X clipboard from tmux running inside an SSH connection to a remote host. Partially borrowed from http://seancoates.com/blogs/remote-pbcopy

Local (OS X) Side

~/Library/LaunchAgents/pbcopy.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
     <key>Label</key>
     <string>localhost.pbcopy</string>
     <key>ProgramArguments</key>
     <array>
         <string>/usr/bin/pbcopy</string>
     </array>
     <key>inetdCompatibility</key>
     <dict>
          <key>Wait</key>
          <false/>
     </dict>
     <key>Sockets</key>
     <dict>
          <key>Listeners</key>
               <dict>
                    <key>SockServiceName</key>
                    <string>2224</string>
                    <key>SockNodeName</key>
                    <string>127.0.0.1</string>
               </dict>
     </dict>
</dict>
</plist>

~/Library/LaunchAgents/pbpaste.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
     <key>Label</key>
     <string>localhost.pbpaste</string>
     <key>ProgramArguments</key>
     <array>
         <string>/usr/bin/pbpaste</string>
     </array>
     <key>inetdCompatibility</key>
     <dict>
          <key>Wait</key>
          <false/>
     </dict>
     <key>Sockets</key>
     <dict>
          <key>Listeners</key>
               <dict>
                    <key>SockServiceName</key>
                    <string>2225</string>
                    <key>SockNodeName</key>
                    <string>127.0.0.1</string>
               </dict>
     </dict>
</dict>
</plist>

~/.ssh/config

Host myhost
    HostName 192.168.1.123
    User myname
    RemoteForward 2224 127.0.0.1:2224
    RemoteForward 2225 127.0.0.1:2225

After adding the PLists above, you'll have to run:

launchctl load ~/Library/LaunchAgents/pbcopy.plist
launchctl load ~/Library/LaunchAgents/pbpaste.plist

Remote (Linux) Side

~/.tmux.conf

if-shell 'test "$(uname)" = "Linux"' 'source ~/.tmux-linux.conf'

~/.tmux-linux.conf

bind C-c run "tmux save-buffer - | pbcopy-remote"
bind C-v run "tmux set-buffer $(pbpaste-remote); tmux paste-buffer"

~/bin/pbpaste-remote

#!/bin/sh
nc localhost 2225

~/bin/pbcopy-remote

#!/bin/sh
cat | nc -q1 localhost 2224

~/.vimrc

function! PropagatePasteBufferToOSX()
  let @n=getreg("*")
  call system('pbcopy-remote', @n)
  echo "done"
endfunction

function! PopulatePasteBufferFromOSX()
  let @+ = system('pbpaste-remote')
  echo "done"
endfunction

nnoremap <leader>6 :call PopulatePasteBufferFromOSX()<cr>
nnoremap <leader>7 :call PropagatePasteBufferToOSX()<cr>
@NHDaly
Copy link

NHDaly commented Aug 30, 2015

Love this! Do you know if this still works for you? I'm having trouble getting the launchd daemon to work on OSX 10.10.5

@mikepqr
Copy link

mikepqr commented Dec 7, 2015

@burke This all works for me perfectly except the final piece of the puzzle: ~/.vimrc. I had to change:

  let @n=getreg("*")

to

  let @n=getreg('"')

and

let @+ = system('pbpaste-remote')

to

let @' = system('pbpaste-remote')

Not sure if this is a vim revision issue. This change was necessary for me on vim 7.4.488.

@devm33
Copy link

devm33 commented Jan 27, 2016

+1000 this is amazing. To follow up from @WilliamsMJ I can confirm his changed work perfectly for modern vim because the default register is "

So if you just want to be able to do <leader>6p to paste from the osx buffer and conversely y<leader>5 to copy to the osx buffer use @" and getreg('"') See my vimrc for actual config

@devm33
Copy link

devm33 commented Jan 30, 2016

Another thing to note is here is that you might want to put pbcopy-remote and pbpaste-remote in somewhere like /usr/bin/ or symlinked there depending on how you start tmux.

If you see something like pbcopy-remote returned error code 127 that means that tmux can't find the script on the path it was started with.

@artnikbrothers
Copy link

I need to work on OS X (remote) server from Linux (local). Can you please help me to solve copy/paste problem in my case?

@loliee
Copy link

loliee commented Aug 2, 2016

Awesome ! Still working on OSX 10.11.6.

@arooni
Copy link

arooni commented Aug 17, 2016

Is there any reason why this wouldn't work across multiple, simultaneous SSH connections with different servers?

It looks like TMUX is working great, but I can't seem to get it working for vim. is yy supposed to be copying => directly into my mac clipboard?

@jondkinney
Copy link

jondkinney commented Oct 13, 2016

@arooni it won't do that without some additional configuration. It can also depend if you have any other plugins running that cannibalize vim's normal yank capabilities. Here's how I have it setup with yankring and using the unnamed clipboard in Vim 7.4.

if has("unix")
  let s:uname = system("uname")
  if s:uname == "Linux\n"
    " Remote Clipboard
    function! PropagatePasteBufferToOSX()
      let @n=getreg('"')

      call system('pbcopy-remote', @n)
      echo "done"
    endfunction

    function! PopulatePasteBufferFromOSX()
      let @" = system('pbpaste-remote')
      echo "done"
    endfunction

    nnoremap <leader>3 :call PopulatePasteBufferFromOSX()<cr>
    nnoremap <leader>2 :call PropagatePasteBufferToOSX()<cr>

    nnoremap yy yy:call PropagatePasteBufferToOSX()<cr>

    function! YRRunAfterMaps()
      nnoremap Y   :<C-U>YRYankCount 'y$'<CR> <bar> :call PropagatePasteBufferToOSX()<CR>
      vnoremap y   y:call PropagatePasteBufferToOSX()<CR>
    endfunction
  endif
endif

@arooni
Copy link

arooni commented Mar 9, 2017

@jondkinney this is awesome ! thanks so much!

question: any way to make your call to PropogatePasteBufferToOSX() asynchronous ? Because of the network delay, and because vim doesn't have background jobs, I notice a slight delay. I tried messing around with tpope's vim-dispatch but couldn't get it working.

thanks again!

@yan12125
Copy link

That's an excellent tutorial. Here I'd like to post some notes: if you use git-master of neovim [1], you can configure the clipboard provider so that conventional shortcuts like "+p "+y works. For example:

    let g:clipboard = {
          \   'name': 'SSH_from_macOS',
          \   'copy': {
          \      '+': 'nc -c localhost 2224',
          \      '*': 'nc -c localhost 2224',
          \    },
          \   'paste': {
          \      '+': 'nc localhost 2225',
          \      '*': 'nc localhost 2225',
          \   },
          \   'cache_enabled': 0,
          \ }

[1] neovim/neovim#6030

@jclosure
Copy link

@jclosure
Copy link

jclosure commented Jul 29, 2019

Incidentally, this approach has problems with multibyte chars like, ü. Seem pbcopy encodes w/ MacRoman.

Example 1:
From Linux->Mac, I send:

Grüssen

In Mac, I get:

Grüssen

Example 2:
From Mac->Linux, I send:

Grüssen

In Linux, I get:

Gr\237ssen

Example 3:
From Linux->Mac->Linux, I send:

Grüssen

In the 2nd Linux box, I get:

Grüssen

My shells in all systems have:

LC_CTYPE=en_US.UTF-8
LANG=en_US.UTF-8

The Linux boxes are using UTF-8 and I think the Mac's pasteboard by default is using MacRoman encoding.

This seems to show that I'm right:
After sending a multibyte char from Linux->Mac, do:

pbpaste | textutil -convert txt -stdin -stdout -encoding 30 | pbcopy

You get:

Grüssen

I've tried doing this conversion in the plist ProgramArguments, but so far no luck. Wonder if anyone else has a better approach to normalize the encoding differences.

Thanks.

@jclosure
Copy link

jclosure commented Jul 30, 2019

Alright, here's a workaround for the encoding issue. Note this makes the assumption that our clipboard server is speaking UTF-8.

pbcopy

	<string>localhost.pbcopy</string>
	<key>ProgramArguments</key>
	<array>
	    <string>/bin/sh</string>
	    <string>-c</string>
	    <string>LC_CTYPE=en_US.UTF-8 pbcopy</string>
	</array>

pbpaste

        <string>localhost.pbpaste</string>
	<key>ProgramArguments</key>
	<array>
	    <string>/bin/sh</string>
	    <string>-c</string>
	    <string>LC_CTYPE=en_US.UTF-8 pbpaste</string>
	</array>

IANA character sets

FYI, you can always debug this using a local file, e.g.:

pbcopy cmd

/bin/sh -c "cat /dev/stdin > /tmp/clipboard.txt; cat /tmp/clipboard.txt | pbcopy"

pbpaste cmd

/bin/sh -c "pbpaste > /tmp/clipboard.txt; cat /tmp/clipboard.txt"

@Midren
Copy link

Midren commented Dec 26, 2021

If you are using tmux-yank, you could just add set -g @override_copy_command 'nc localhost 2224', as mentioned here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment