In previous post of “tmux in practice” series we’ve discussed various solutions to share copied text from tmux session to system clipboard. While this is rather easy to setup when it comes to local session (just pipe selected text to pbcopy
or xclip
or xsel
), things get complicated when you work with remote tmux session. You need some mechanizm to transport data from remote machine to local system’s clipboard. You’re lucky if your terminal emulator handles OSC 52 ANSI escape sequences. However, only few terminal emulators support this feature: it would work out of the box on OSX in iTerm, and most likely fail on Linux (unless you’re using basic xterm).
Today, let’s explore alternative solution. It consists of following pieces:
xclip
thus storing it in a system clipboard.So we need to setup network listener on some port. When connection is made, any input should be piped to xclip
to get stored on system clipboard. This service is going to run permanently, so our choice would be creating a systemd socket-activated service. If you’re on OSX, use launchd instead of systemd (see paragraph regarding OSX below).
The easiest way is to create two unit files: service unit and socket unit. Put your unit files in /etc/systemd/system
directory. Let start with /etc/systemd/system/xclip.socket
file:
[Unit]Description=Network copy backend for tmux based on xclip
[Socket]ListenStream=19988Accept=yes
[Install]WantedBy=sockets.target
The unit file just says this is a network socket listening on port 19988. Service design varies much depending on Accept
settting:
yes
, systemd will accept incoming connections, and pass connection socket to the target service. The socket can then be wired to stdin and stdout file descriptors of service’s process. Service is started lazily on first connection. New service instance will be spawned for each incoming connection (templated units are used under the hood)no
, systemd will not accept connection, listening socket will be passed to the service. Program has to be tailored so it can process those sockets (using sd_listen_fds
function). Only one instance of service will be spawned regardless number of connection (singleton service). Service is lazily activated, and is started on first incoming connection.Let’s stick with Accept=yes
design, because it’s very straightforward to just use xclip
command without any modifications and just wire connection socket to xclip’s stdin and stdout descriptors.
Now, let’s create /etc/systemd/system/[email protected]
. Note, it should be template unit file as soon as Accept=yes
puts this requirement.
[Unit]Description=Copy backend service piping input to xclip
[Service]Type=simpleExecStart=/usr/bin/xclip -i -f -selection primary | /usr/bin/xclip -i -selection clipboardStandardInput=socketStandardOutput=socket
We use familiar xclip command to store data in a primary and clipboard selections.
Now, let’s enable and start our socket unit. enable
- means it will be automatically started on next system boot, start
- means we manually kick it off right now.
$ sudo systemctl enable xclip.socketCreated symlink from /etc/systemd/system/sockets.target.wants/xclip.socket to /etc/systemd/system/xclip.socket.
$ sudo systemctl start xclip.socket
$ sudo systemctl status xclip.socket
● xclip.socket - XClip socketLoaded: loaded (/etc/systemd/system/xclip.socket; enabled; vendor preset: disabled)Active: active (listening) since Mon 2017-11-27 15:07:12 EET; 3s agoListen: [::]:19988 (Stream)Accepted: 0; Connected: 0
Nov 27 15:07:12 centos7 systemd[1]: Listening on XClip socket.Nov 27 15:07:12 centos7 systemd[1]: Starting XClip socket.
We see our socket unit is started. Accepted
indicates total number of connection made since start of service, Connected
indicates current number of active connections. Let’s ensure port 19988 is listening:
$ ss -tnl '( sport = 19988 )'
State Recv-Q Send-Q Local Address:Port Peer Address:PortLISTEN 0 128 :::19988 :::*
We can test our service using netcat
. Any data you send to it should land in system clipboard. Test it before we go further.
echo "text to copy" | nc localhost 19988
Note, if you curious about service instance unit state, you will not find any running instance via systemctl list-units
. That’s because it spawns and almost immediately exits as soon as xclip process exits.
Use following command to setup SSH remote tunnel while you’re connecting to remote machine:
ssh -R 19988:localhost:19988 [email protected]
Or you can set it once in your ~/.ssh/config
file:
Host vb_ubuntu14Hostname 192.168.33.100User alexeysIdentityFile ~/.ssh/alexeys_at_vb_ubuntu14RemoteForward 19988 localhost:19988
and then just:
ssh vb_ubuntu14
SSH remote tunnels lets application on remote network to talk to service on local network on particular port (in our case, localhost:19988). We’re using same port numbers both on local and remote machine to avoid mess.
Now, when we have all pieces ready, we can wire them to our ~/.tmux.conf.
Lets extend yank.sh
file we crafted in previous part of “tmux in practice”:
# Resolve copy backend: pbcopy (OSX), reattach-to-user-namespace (OSX), xclip/xsel (Linux), or network service
_# get data either form stdin or from file_buf=$(cat "$@")
copy_backend=""if is_app_installed pbcopy; thencopy_backend="pbcopy"elif is_app_installed reattach-to-user-namespace; thencopy_backend="reattach-to-user-namespace pbcopy"elif [ -n "${DISPLAY-}" ] && is_app_installed xsel; thencopy_backend="xsel -i --clipboard"elif [ -n "${DISPLAY-}" ] && is_app_installed xclip; thencopy_backend="xclip -i -f -selection primary | xclip -i -selection clipboard"elif [ "$(ss -n -4 state listening "( sport = 19988 )" | tail -n +2 | wc -l)" -eq 1 ]; thencopy_backend="nc localhost 19988"fi
_# if copy backend is resolved, copy and exit_if [ -n "$copy_backend" ]; thenprintf "$buf" | eval "$copy_backend"exit;fi
Here we have branching logic to select clipboard backend where we should pipe a selected text. The latter elif
checks if anybody listens on port 19988
, and netcats data to this port.
Keybindings in ~/.tmux.conf
:
yank="~/.tmux/yank.sh"
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; tmux paste-buffer”bind-key -T copy-mode-vi D send-keys -X copy-end-of-line \; run "tmux save-buffer - | $yank"bind-key -T copy-mode-vi A send-keys -X append-selection-and-cancel \; run "tmux save-buffer - | $yank"
To be honest, solution isn’t lightweight like a breeze. Moreover, it comes with limitations.
If you start more that one SSH session (from same local machine or from different machines) to same remote machine with same port forwarding configuration, only first connection will have text copying working properly. In this case you need to have different remote ports tunnelled to same local port, and change tmux.conf
to probe several ports.
Everything said above is true for OSX, except that instead systemd you’re going to use launchd, and usepbcopy
rather than xclip.
Here is an example of equivalent launchd service:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>local.pbcopy</string><key>UserName</key><string>asamoshkin</string><key>Program</key><string>/usr/bin/pbcopy</string><key>Sockets</key><dict><key>Listeners</key><dict><key>SockNodeName</key><string>localhost</string><key>SockServiceName</key><string>19988</string></dict></dict><key>inetdCompatibility</key><dict><key>Wait</key><false/></dict></dict></plist>
To install and start it:
$ launchctl load local.pbcopy.plist$ launchctl start local.pbcopy
You can see all this stuff in action by checking out my tmux-config repo.
macos — How do I copy to the OSX clipboard from a remote shell using iTerm2? — Ask Different — https://apple.stackexchange.com/questions/257609/how-do-i-copy-to-the-osx-clipboard-from-a-remote-shell-using-iterm2
macos — Synchronize pasteboard between remote tmux session and local Mac OS pasteboard — Super User — https://superuser.com/questions/407888/synchronize-pasteboard-between-remote-tmux-session-and-local-mac-os-pasteboard/408374#408374
linux — Getting Items on the Local Clipboard from a Remote SSH Session — Stack Overflow — https://stackoverflow.com/questions/1152362/getting-items-on-the-local-clipboard-from-a-remote-ssh-session
systemd for Administrators, Part XI, inetd services — http://0pointer.de/blog/projects/inetd.html
An example inetd-like socket-activated service. #systemd #inetd #systemd.socket — https://gist.github.com/drmalex07/28de61c95b8ba7e5017c
Systemd socket files stdin redirection / System Administration / Arch Linux Forums — https://bbs.archlinux.org/viewtopic.php?id=207834
samoshkin/tmux-config: Tmux configuration, that supercharges your tmux to build cozy and cool terminal environment — https://github.com/samoshkin/tmux-config