joshrosso

Clipboard Sharing to a Remote Dev Environment

I run my development environment in portable and reproducible virtual machine built with nix. Once the VM is started, accessing all my projects is just a matter of using ssh, or mosh depending on what I’m feeling that day. The biggest, and perhaps only, pain point with my approach is clipboard sharing between client (my laptop) and server (my VM). For example, when I’m copying logs from the VM’s tmux session or copying lines in neovim, I want to have those instantly in the clipboard of my client machine.

There are are a variety of ways to share a clipboard in popular virtual machine applications like Fusion or Parallels. However, these approaches don’t work for me for a few reasons:

  1. It’s not portable: While I usually run my VM locally, sometimes I’ll do it in a cloud provider.
  2. My VM does not run a desktop environment, thus there is no concept of a clipboard.

Like many things in software, I’ve gone searching for a solution only to come back to a technology that’s been around since (at least) the 80s. This technology is the named pipe. Effectively I used this persistent pipe/queue as a pseudo clipboard to facilitate sharing and today I’m going to explain how.

This post assumes you understand how a named pipe works. But, if you don’t have experience with named pipes, checking my post on Pipes: Named and Unnamed to dig into how they work.

Attachment

Attaching my client machine to the VM is a matter of ensuring:

  1. The named pipe exists.
  2. The client can “connect and listen” to the pipe.
  3. When data is in the pipe, it reads it, and puts it in the guest clipboard.

Here’s a visual representation of this idea:

VM
VM
~/clip
( named pipe
file )
~/clip...
nvim
nvim
Copy Executed
Copy Executed
tmux
tmux
ssh
ssh
Client
Client
SSH Connection
SSH Connection
alacritty
(terminal)
alacritty...
SSH Connection
SSH Connection
listener script
listener script
Text is not SVG - cannot display

In this model, there are 2 SSH connections. One where I’m accessing my work environment using tmux and altering files with nvim. The other listens for data to be placed in the named piped located at ~/clip.

For setup in the VM, I first create the named pipe ~/clip:

mkfifo ~/clip

Next, I need to ensure that any copy request coming out of tmux or nvim forwards the data to this named pipe. For tmux, I add this to ~/.config/tmux.conf:

set -g mouse on
setw -g mode-keys vi

# map vi keys when in copy-mode for selection and copy (yank)
bind -T copy-mode-vi 'v' send -X begin-selection
bind -T copy-mode-vi 'V' send -X select-line
bind -T copy-mode-vi 'r' send -X rectangle-toggle

# send buffer to ~/clip when copying with 'y'
bind -T copy-mode-vi 'y' send -X copy-pipe-and-cancel "tmux show-buffer > ~/clip"
# send buffer to ~/clip when dragging with the mouse
bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel\; run "tmux show-buffer > ~/clip"

The key pieces in the above are the copy-mode-vi settings that run tmux show-buffer > ~/clip on specific events.

For nvim, I setup a dedicated command that will forward selected content to ~/clip. I intentionally choose not to hook into yank or override any register behaviors because I want to keep those as is. Here are the relevant parts of my function written in lua stored in init.vim:

function SaveSelectionToClipboard()
    -- Save the current selection to the ~/clip file
    vim.cmd([[ '<,'>w! >> ~/clip ]])
  end

  -- Create a mapping to call the function in Visual mode
  vim.api.nvim_set_keymap('v', '<leader>c', ':lua SaveSelectionToClipboard()<CR>', { noremap = true })

I believe the equivalent in vimscript would look something like:

vnoremap <silent> <your_hotkey> :w! ~/clip<CR>

The last step is to connect the client machine (laptop) to the VM. This should likely be done by using an init system like systemd or homebrew/launchctl to run it as a service that constantly triggers. However, for the sake of a generic example, here’s how you can connect using a simple bash loop:

while true
  do ssh -i ~/.ssh/joshrosso.pem 192.168.1.77 'cat ~/clip' | pbcopy
done

In the above, you’ll change 192.168.1.77 to your host’s IP address. Additionally, if not using MacOS, you’ll swap pbcopy out for something that saves piped output to your clipboard, like xclip. If you want to prevent password prompting for the ssh command, you should add this host and identity file to your ~/.ssh/config.

Now you’re set to trigger copies on your VM and you should see the contents get forwarding to your client’s clipboard.

Conclusion

Hope you enjoyed this random but interesting use case for sharing clipboards between a guest and host machine!

Contents