tmux en la práctica: integración con el portapapeles del sistema

Cómo construir un puente entre el búfer de copia tmux y el portapapeles del sistema, y ​​almacenar el texto seleccionado en el portapapeles del sistema OSX o Linux, de una manera que aborde los escenarios de uso local y remoto

Esta es la cuarta parte de mi serie de artículos sobre tmux en la práctica.

En la parte anterior de la serie "tmux en la práctica" hablamos sobre cosas como el búfer de desplazamiento hacia atrás, el modo de copia y tocamos ligeramente el tema de la copia de texto en el búfer de copia de tmux.

Tarde o temprano se dará cuenta de que todo lo que copie en tmux se almacena solo en el búfer de copia de tmux, pero no se comparte con el portapapeles del sistema. Copiar y pegar son operaciones tan comunes, que esta limitación en sí misma es suficiente para convertir tmux en un ladrillo inútil, a pesar de otras ventajas.

En esta publicación, exploraremos cómo construir un puente entre el búfer de copia tmux y el portapapeles del sistema, para almacenar el texto copiado en el portapapeles del sistema, de una manera que aborde los escenarios de uso local y remoto.

Discutiremos las siguientes técnicas:

  1. Solo OSX, comparte texto con el portapapeles usando "pbcopy"
  2. Solo OSX, utilizando el contenedor "reattach-to-user-namespace" para que pbcopy funcione correctamente dentro del entorno tmux
  3. Solo Linux, comparte texto con la selección X usando los comandos xclipoxsel

Las técnicas anteriores abordan solo escenarios locales.

Para admitir escenarios remotos, existen 2 métodos adicionales:

  1. Utilice la secuencia de escape ANSI OSC 52 para hablar con el terminal principal / controlador para administrar y almacenar texto en un portapapeles de una máquina local.
  2. Configure un oyente de red local que canalice la entrada a pbcopyo xclipo xsel. La canalización copió el texto seleccionado de la máquina remota a un oyente en la máquina local a través del túnel remoto SSH. Esto es bastante complicado, y dedicaré una publicación dedicada a describirlo.

OSX. comandos pbcopy y pbpaste

pbcopyy los pbpastecomandos le permiten interactuar y manipular el portapapeles del sistema desde la línea de comandos.

pbcopy lee datos stdiny los almacena en el portapapeles. pbpastehace lo contrario y coloca el texto copiado stdout.

La idea es conectarse a varios comandos tmux, que logran copiar texto en modo copia.

Vamos a enumerarlos:

$ tmux -f /dev/null list-keys -T copy-mode-vi
bind-key -T copy-mode-vi Enter send-keys -X copy-selection-and-cancelbind-key -T copy-mode-vi C-j send-keys -X copy-selection-and-cancelbind-key -T copy-mode-vi D send-keys -X copy-end-of-linebind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-selection-and-cancelbind-key -T copy-mode-vi A send-keys -X append-selection-and-cancel

copy-selection-and-cancely copy-end-of-lineson comandos especiales de tmux que tmux entiende cuando el panel está en modo de copia. Hay dos tipos de comando de copia: copy-selectiony copy-pipe.

Reescribamos la Entercombinación de teclas con el comando copy-pipe:

bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "pbcopy"

copy-pipecomando almacena el texto seleccionado en el búfer tmux igual que copy-selection, además de canalizar el texto seleccionado al comando dado pbcopy. Entonces obtenemos el texto almacenado en dos lugares: el búfer de copia tmux y el portapapeles del sistema.

OSX. volver a adjuntar al contenedor de espacio de nombres de usuario

Hasta aquí todo bien. Sin embargo, en algunas versiones de OSX, pbcopyy pbpaste no funcionan correctamente cuando se ejecuta en tmux.

Lea más detalles de Chris Johnsen sobre por qué sucede:

tmux usa la función de biblioteca daemon (3) al iniciar su proceso de servidor. En Mac OS X 10.5, Apple cambió el daemon (3) para mover el proceso resultante de su espacio de nombres de arranque original al espacio de nombres de arranque raíz. Esto significa que el servidor tmux, y sus hijos, perderán de forma automática e incontrolable el acceso a lo que habría sido su espacio de nombres bootstrap original (es decir, el que tiene acceso al servicio de escritorio).

Una solución común es utilizar el contenedor reattach-to-user-namespace. Esto nos permite iniciar un proceso y que ese proceso se adjunte al espacio de nombres de arranque por usuario, lo que hace que el programa se comporte como esperamos. Necesita cambiar la combinación de teclas correctamente:

bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel “reattach-to-user-namespace pbcopy”

Además, necesitaría decirle a tmux que ejecute su shell (bash, zsh, ...) dentro de un contenedor, configurando la default-commandopción:

if -b "command -v reattach-to-user-namespace > /dev/null 2>&1" \ "run 'tmux set -g default-command \"exec $(tmux show -gv default-shell) 2>/dev/null & reattach-to-user-namespace -l $(tmux show -gv default-shell)\"'"

Nota : algunas versiones de OSX funcionan bien incluso sin este truco (OSX 10.11.5 El Capitan), mientras que los usuarios de OSX Sierra informan que este truco todavía es necesario.

Linux. Interactuar con la selección X a través de xclip y xsel

Podemos hacer uso de comandos xclipo xselen Linux para almacenar texto en el portapapeles, al igual que pbcopyen OSX. En Linux, hay varios tipos de selecciones de portapapeles mantenidas por el servidor X: primario, secundario y portapapeles. Solo nos ocupamos de primaria y portapapeles. La secundaria se pensó como una alternativa a la primaria.

bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "xclip -i -f -selection primary | xclip -i -selection clipboard"

O al usar xsel:

bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "xsel -i --clipboard"

Lea aquí sobre la comparación de xclipvs. xsel, si tiene curiosidad. Además, consulte esta publicación sobre xclipuso y ejemplos. Y no olvide instalar una de estas utilidades, ya que es posible que no formen parte de su distribución.

Usando la secuencia de escape ANSI OSC 52 para hacer que la terminal almacene texto en el portapapeles

Hasta ahora cubrimos solo escenarios locales. Cuando conecta SSH a una máquina remota e inicia sesiones tmux allí, no puede utilizarlo pbcopy, xclipo xselporque el texto se almacenará en el portapapeles de la máquina remota, no en el local. Necesita alguna forma de transportar el texto copiado al portapapeles de su máquina local.

La secuencia de escape ANSI es una secuencia de bytes enviados al terminal que se entrelazan con caracteres imprimibles regulares y se utilizan para controlar varios aspectos del terminal: como colores de texto, posición del cursor, efectos de texto, pantalla de borrado. El terminal es capaz de detectar tal secuencia de control de bytes que hace que active acciones específicas y no imprima esos caracteres en la salida.

La secuencia de escape ANSI se puede detectar ya que comienzan con un ESCcarácter ASCII (0x1b hexadecimal, 027 decimal, \ 033 en octal). Por ejemplo, cuando el terminal ve la \033[2Asecuencia, moverá la posición del cursor 2 líneas hacia arriba.

Realmente hay muchas de esas secuencias conocidas. Algunos de ellos son iguales en diferentes tipos de terminales, mientras que otros pueden variar y ser muy específicos de su emulador de terminal. Utilice el infocmpcomando para consultar la terminfobase de datos en busca de secuencias de escape compatibles con diferentes tipos de terminales.

Muy bien, pero ¿cómo puede ayudarnos con el portapapeles? Resulta que hay una categoría especial de secuencias de escape: “Controles del sistema operativo” (OSC) y la secuencia de escape “OSC 52”, que permite que las aplicaciones interactúen con el portapapeles.

Si está utilizando iTerm, intente ejecutar el siguiente comando y luego " ⌘V" para ver el contenido del portapapeles del sistema. Asegúrese de activar el manejo de la secuencia de escape OSC 52: "Preferencias -> General -> Las aplicaciones en la terminal pueden acceder al portapapeles".

printf "\033]52;c;$(printf "%s" "blabla" | base64)\a"

La conclusión es que podemos almacenar texto en el portapapeles del sistema enviando una secuencia de escape ANSI especialmente diseñada a nuestro terminal.

Escribamos el script de shell yank.sh:

#!/bin/bash
set -eu
# get data either form stdin or from filebuf=$(cat "[email protected]")
# Get buffer lengthbuflen=$( printf %s "$buf" | wc -c )
maxlen=74994
# warn if exceeds maxlenif [ "$buflen" -gt "$maxlen" ]; then printf "input is %d bytes too long" "$(( buflen - maxlen ))" >&2fi
# build up OSC 52 ANSI escape sequenceesc="\033]52;c;$( printf %s "$buf" | head -c $maxlen | base64 | tr -d '\r\n' )\a"

Entonces, leemos el texto para copiar stdin, luego verificamos si su longitud excede la longitud máxima de 74994 bytes. Si es cierto, lo recortamos y finalmente convertimos los datos a base64 y los ajustamos en la secuencia de escape OSC 52:\033]53;c;${data_in_base64}\a

Entonces conectemos con nuestras combinaciones de teclas tmux. Eso es bastante fácil: simplemente canalice el texto seleccionado a nuestro yank.shscript, tal como lo canalizamos a pbcopyo xclip.

yank="~/.tmux/yank.sh"
bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "$yank"

However, there is one piece left to complete the puzzle. Where should we send the escape sequence? Apparently, just sending it to stdout won’t work. The target should be our parent terminal emulator, but we don’t know the right tty. So, we’re going to send it to tmux’s active pane tty, and tell tmux to further resend it to the parent terminal emulator:

# build up OSC 52 ANSI escape sequenceesc="\033]52;c;$( printf %s "$buf" | head -c $maxlen | base64 | tr -d '\r\n' )\a"esc="\033Ptmux;\033$esc\033\\"
pane_active_tty=$(tmux list-panes -F "#{pane_active} #{pane_tty}" | awk '$1=="1" { print $2 }')
printf "$esc" > "$pane_active_tty"

We use tmux list-panes command to query for the active pane and it’s tty. We also put our OSC 52 sequence in an additional wrapper escape sequence (Device Control String, ESC P), so tmux unwraps this envelope and passes OSC 52 to parent terminal.

In newer versions of tmux, you can tell tmux to handle interactions with the clipboard for you. Seeset-clipboard tmux option. on — tmux will create an inner buffer and attempt to set the terminal clipboard using OSC 52. external — do not create a buffer, but still attempt to set the terminal clipboard.

Just make sure it’s either external or on:

set -g set-clipboard on

So, if tmux is already capable of this feature, why we need to bother ourselves with manual wiring OSC 52 stuff? That’s because set-clipboard does not work when you have a remote tmux session nested in a local one. And it only works in those terminals which supports OSC 52 escape sequence handling.

The trick for nested remote sessions is to bypass the remote session and send our OSC 52 escape sequence directly to the local session, so it hits our local terminal emulator (iTerm).

Use $SSH_TTY for this purpose:

# resolve target terminal to send escape sequence# if we are on remote machine, send directly to SSH_TTY to transport escape sequence# to terminal on local machine, so data lands in clipboard on our local machinepane_active_tty=$(tmux list-panes -F "#{pane_active} #{pane_tty}" | awk '$1=="1" { print $2 }')target_tty="${SSH_TTY:-$pane_active_tty}"
printf "$esc" > "$target_tty"

That’s it. Now we have a completely working solution, be it a local session, remote or both, nested in each other. Credits to this great post, where I first read about this approach.

The major drawback of using OSC escape sequences,is that despite being declared in spec, only a few terminals support this in practice: iTerm and xterm do, whereas OSX Terminal, Terminator, and Gnome terminal does not. So, an otherwise great solution (especially in remote scenarios, when you cannot just pipe to xclip or pbcopy) lacks wider terminal support.

You might want to checkout complete version of yank.sh script.

There is yet another solution to support remote scenarios, which is rather crazy, and I’ll describe it in another dedicated post. The idea is to setup a local network listener which pipes input to pbcopy or xclipor xsel; and pipes copied selected text from a remote machine to a listener on the local machine through SSH remote tunneling. Stay tuned.

Resources and links

ANSI escape code — Wikipedia — //en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences

What are OSC terminal control sequences / escape codes? | ivucica blog — //blog.vucica.net/2017/07/what-are-osc-terminal-control-sequences-escape-codes.html

Copying to clipboard from tmux and Vim using OSC 52 — The Terminal Programmer — //sunaku.github.io/tmux-yank-osc52.html

Copy Shell Prompt Output To Linux / UNIX X Clipboard Directly — nixCraft — //www.cyberciti.biz/faq/xclip-linux-insert-files-command-output-intoclipboard/

software recommendation — ‘xclip’ vs. ‘xsel’ — Ask Ubuntu — //askubuntu.com/questions/705620/xclip-vs-xsel

Everything you need to know about Tmux copy paste · rushiagr — //www.rushiagr.com/blog/2016/06/16/everything-you-need-to-know-about-tmux-copy-pasting/

macos — Synchronize pasteboard between remote tmux session and local Mac OS pasteboard — Super User — //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 — //stackoverflow.com/questions/1152362/getting-items-on-the-local-clipboard-from-a-remote-ssh-session

Use tmux set-clipboard in gnome-terminal (XTerm’s disallowedWindowOps) — Ask Ubuntu — //askubuntu.com/questions/621522/use-tmux-set-clipboard-in-gnome-terminal-xterms-disallowedwindowops/621646