Yet another way to copy text from remote session into local clipboard In we’ve discussed various solutions to share copied text from session to system clipboard. While this is rather easy to setup when it comes to local session (just pipe selected text to or or ), 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 . However, only few terminal emulators support this feature: it would work out of the box on OSX in iTerm, and most likely fail on (unless you’re using basic xterm). previous post of “tmux in practice” series tmux pbcopy xclip xsel OSC 52 ANSI escape sequences Linux Today, let’s explore alternative solution. It consists of following pieces: On local machine we’re going to setup systemd service, listening on a network socket, that pipes any received input to thus storing it in a system clipboard. xclip Setup SSH remote tunnel, connecting port on remote machine with port on local machine, where our service listens on. Change remote tmux configuration to pipe selected text to network port on remote machine, so it gets transported to local machine via SSH remote tunnel. Socket-activated xclip systemd service So we need to setup network listener on some port. When connection is made, any input should be piped to 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). xclip The easiest way is to create two unit files: service unit and socket unit. Put your unit files in directory. Let start with file: /etc/systemd/system /etc/systemd/system/xclip.socket [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 settting: Accept , 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) yes , 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 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. no sd_listen_fds Let’s stick with design, because it’s very straightforward to just use command without any modifications and just wire connection socket to xclip’s stdin and stdout descriptors. Accept=yes xclip Now, let’s create . Note, it should be template unit file as soon as puts this requirement. /etc/systemd/system/xclip@.service Accept=yes [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. - means it will be automatically started on next system boot, - means we manually kick it off right now. enable start $ 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. indicates total number of connection made since start of service, indicates current number of active connections. Let’s ensure port 19988 is listening: Accepted Connected $ ss -tnl '( sport = 19988 )' State Recv-Q Send-Q Local Address:Port Peer Address:PortLISTEN 0 128 :::19988 :::* We can test our service using . Any data you send to it should land in system clipboard. Test it before we go further. netcat echo "text to copy" | nc localhost 19988 Note, if you curious about service instance unit state, you will not find any running instance via . That’s because it spawns and almost immediately exits as soon as xclip process exits. systemctl list-units SSH remote tunnel Use following command to setup SSH remote tunnel while you’re connecting to remote machine: ssh -R 19988:localhost:19988 alexeys@192.168.33.101 Or you can set it once in your file: ~/.ssh/config 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. tmux configuration Now, when we have all pieces ready, we can wire them to our Lets extend file we crafted in previous part of “tmux in practice”: ~/.tmux.conf. yank.sh # 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 checks if anybody listens on port , and netcats data to this port. elif 19988 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" ; ; Limitations 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 to probe several ports. tmux.conf What’s up with OSX? Everything said above is true for OSX, except that instead systemd you’re going to use launchd, and use rather than pbcopy 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" " "><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> http://www.apple.com/DTDs/PropertyList-1.0.dtd 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 repo. tmux-config Resources and links 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