tmux i praksis: integration med systemudklipsholder

Sådan bygger du en bro mellem tmux-kopibuffer og systemudklipsholder og gemmer valgt tekst på OSX- eller Linux-systemudklipsholder på en måde, der adresserer både lokale og eksterne brugsscenarier

Dette er den 4. del af min tmux i praksis artikelserie.

I den forrige del af "tmux i praksis" -serien talte vi om ting som scrollback-buffer, kopitilstand og lidt berørte emnet kopiering af tekst til tmux's kopibuffer.

Før eller senere vil du indse, at det, du kopierer i tmux, kun gemmes i tmux's kopibuffer, men ikke deles med systemets udklipsholder. Kopiering og indsættelse er så almindelige operationer, at denne begrænsning i sig selv er nok til at gøre tmux til en ubrugelig mursten på trods af andre godbidder.

I dette indlæg undersøger vi, hvordan man bygger en bro mellem tmux-kopibufferen og systemudklipsholderen, for at gemme kopieret tekst på systemudklipsholderen på en måde, der adresserer både lokale og eksterne brugsscenarier.

Vi diskuterer følgende teknikker:

  1. Kun OSX, del tekst med udklipsholder ved hjælp af "pbcopy"
  2. Kun OSX ved hjælp af "reattach-to-user-namespace" indpakning for at få pbcopy til at fungere korrekt i tmux miljø
  3. Kun Linux, del tekst med X-valg ved hjælp af xclipeller xselkommandoer

Ovenstående teknikker adresserer kun lokale scenarier.

For at understøtte fjernscenarier er der to ekstra metoder:

  1. Brug Escape-sekvensen ANSI OSC 52 til at tale med kontrollerende / overordnede terminal til at administrere og gemme tekst på et udklipsholder på en lokal maskine.
  2. Opsæt en lokal netværkslytter, der sender input til pbcopyeller xclipeller xsel. Rørkopieret valgt tekst fra fjernmaskine til en lytter på den lokale maskine gennem SSH fjerntunnel. Dette er snarere involveret, og jeg vil afsætte et dedikeret indlæg til at beskrive det.

OSX. kommandoer pbcopy og pbpaste

pbcopyog pbpastekommandoer giver dig mulighed for at interagere og manipulere systemets udklipsholder fra kommandolinjen.

pbcopy læser data fra stdinog gemmer dem på udklipsholderen. pbpastegør det modsatte og sætter kopieret tekst på stdout.

Ideen er at tilslutte sig forskellige tmux-kommandoer, der formår at kopiere tekst i kopitilstand.

Lad os liste dem:

$ 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-cancelog copy-end-of-lineer specielle tmux-kommandoer, som tmux forstår, når ruden er i kopitilstand. Der er to varianter af kopikommandoen: copy-selectionog copy-pipe.

Lad os omskrive Enternøglebinding med copy-pipe-kommando:

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

copy-pipekommando gemmer markeret tekst i tmux-buffer som den samme copy-selection, plus rør markeret tekst til den givne kommando pbcopy. Så vi får tekst gemt to steder: tmux-kopibufferen og systemets udklipsholder.

OSX. vedhæft igen-til-bruger-navneområdet indpakning

Så langt så godt. Men på nogle versioner af OSX, pbcopyog pbpaste undlader at fungere korrekt, når køre under tmux.

Læs flere detaljer fra Chris Johnsen om, hvorfor det sker:

tmux bruger daemon (3) biblioteksfunktionen, når den starter sin serverproces. I Mac OS X 10.5 ændrede Apple dæmon (3) for at flytte den resulterende proces fra sit originale bootstrap-navneområde til root-bootstrap-navneområdet. Dette betyder, at tmux-serveren og dens børn automatisk og ukontrollabelt mister adgang til, hvad der ville have været deres originale bootstrap-navneområde (dvs. den, der har adgang til bordtjenesten).

En almindelig løsning er at bruge omslutning til bruger-namespace-rum. Dette giver os mulighed for at starte en proces og få den proces knyttet til det per-bruger bootstrap navneområde, hvilket får programmet til at opføre sig som vi forventer. Du skal ændre tastebinding korrekt:

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

Plus, du bliver nødt til at fortælle tmux at køre din shell (bash, zsh, ...) inde i en indpakning ved at indstille default-commandindstilling:

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)\"'"

Bemærk : nogle OSX-versioner fungerer fint selv uden dette hack (OSX 10.11.5 El Capitan), mens OSX Sierra-brugere rapporterer, at dette hack stadig er nødvendigt.

Linux. Interagere med X-valg via xclip og xsel

Vi kan bruge xclipeller xselkommandoer på Linux til at gemme tekst i udklipsholderen, ligesom pbcopypå OSX. På Linux er der flere slags udklipsholdervalg vedligeholdt af X-server: primær, sekundær og udklipsholder. Vi bekymrer os kun om primær og udklipsholder. Sekundær var ment som en alternativ til primær.

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

Eller når du bruger xsel:

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

Læs her om sammenligning af xclipvs. xsel, hvis du er nysgerrig. Tjek også dette indlæg om xclipbrug og eksempler. Og glem ikke at installere et af disse værktøjer, da de måske ikke er en del af din distribution.

Brug af ANSI OSC 52 escape-sekvens til at få terminal til at gemme tekst i udklipsholderen

Indtil videre dækkede vi kun lokale scenarier. Når du SSH til fjernmaskine og starter tmux-sessioner der, kan du ikke gøre brug af pbcopy, xclipeller xselfordi tekst gemmes i fjernmaskinens udklipsholder, ikke i din lokale. Du har brug for en måde at transportere kopieret tekst til din lokale maskines udklipsholder.

ANSI escape-sekvens er en sekvens af bytes sendt til terminalen, der er sammenflettet med regelmæssige printbare tegn og bruges til at styre forskellige terminalaspekter: såsom tekstfarver, markørposition, teksteffekter, rydningsskærm. Terminalen er i stand til at detektere en sådan styrende sekvens af bytes, der får den til at udløse specifikke handlinger og ikke udskrive disse tegn til output.

The ANSI escape sequence can be detected as they start with ESC ASCII character (0x1b hex, 027 decimal, \033 in octal). For example, when the terminal sees the \033[2A sequence, it will move the cursor position 2 lines up.

There are really a lot of those known sequences. Some of them are the same across different terminal types, while others can vary and be very specific to your terminal emulator. Useinfocmp command to query terminfo database for escape sequences supported by different types of terminals.

Okay great, but how can it help us regarding the clipboard? It turns out that there is a special category of escape sequences: “Operating System Controls” (OSC) and the “OSC 52" escape sequence, which allows applications to interact with the clipboard.

If you’re using iTerm, try to execute following command, and then “⌘V” to see contents of system clipboard. Make sure to turn on OSC 52 escape sequence handling: “Preferences -> General -> Applications in terminal may access clipboard”.

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

The conclusion is that we can store text in the system clipboard by sending a specially crafted ANSI escape sequence to our terminal.

Let’s write the shell script 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"

So, we read text to copy from stdin, then check if it’s length exceeds the maximum length of 74994 bytes. If true, we crop it, and finally convert data to base64 and wrap in OSC 52 escape sequence: \033]53;c;${data_in_base64}\a

Then let’s wire it with our tmux keybindings. That’s pretty easy: just pipe the selected text to our yank.sh script, just as we pipe it to pbcopy or 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

Brug tmux set-udklipsholder i gnome-terminal (XTerm's disallowedWindowOps) - Spørg Ubuntu - //askubuntu.com/questions/621522/use-tmux-set-clipboard-in-gnome-terminal-xterms-disallowedwindowops/621646