tmux i praksis: integrering med systemutklippstavlen

Hvordan bygge en bro mellom tmux kopibuffer og system utklippstavle, og lagre valgt tekst på OSX eller Linux system utklippstavle, på en måte som adresserer både lokale og eksterne bruksscenarier

Dette er den fjerde delen av min tmux i praksis artikkelserie.

I den forrige delen av "tmux i praksis" -serien snakket vi om ting som tilbakeslagsbuffer, kopimodus og litt berørt temaet å kopiere tekst til tmuxs kopibuffer.

Før eller siden vil du innse at det du kopierer i tmux, blir bare lagret i tmuxs kopibuffer, men ikke delt med systemutklippstavlen. Kopiering og liming er så vanlige operasjoner at denne begrensningen i seg selv er nok til å gjøre tmux til en ubrukelig murstein, til tross for andre godbiter.

I dette innlegget vil vi utforske hvordan du bygger en bro mellom tmux-kopibufferen og systemutklippstavlen, for å lagre kopiert tekst på systemutklippstavlen, på en måte som adresserer både lokale og eksterne bruksscenarier.

Vi diskuterer følgende teknikker:

  1. Bare OSX, del tekst med utklippstavlen ved hjelp av "pbcopy"
  2. Bare OSX, ved å bruke "reattach-to-user-namespace" wrapper for å få pbcopy til å fungere skikkelig i tmux-miljøet
  3. Bare Linux, del tekst med X-valg ved hjelp av xclipeller xselkommandoer

Teknikker ovenfor adresserer bare lokale scenarier.

For å støtte eksterne scenarier er det to ekstra metoder:

  1. Bruk ANSI OSC 52 escape-sekvensen til å snakke med kontrollerende / overordnet terminal for å administrere og lagre tekst på et utklippstavle på en lokal maskin.
  2. Sett opp en lokal nettverkslytter som sender input til pbcopyeller xclipeller xsel. Rørkopierte valgt tekst fra ekstern maskin til en lytter på den lokale maskinen gjennom SSH ekstern tunneling. Dette er ganske involvert, og jeg vil vie et dedikert innlegg for å beskrive det.

OSX. kommandoer pbcopy og pbpaste

pbcopyog pbpastekommandoer lar deg samhandle og manipulere utklippstavlen fra kommandolinjen.

pbcopy leser data fra stdinog lagrer dem på utklippstavlen. pbpastegjør det motsatte og setter kopiert tekst på stdout.

Ideen er å koble til forskjellige tmux-kommandoer som klarer å kopiere tekst mens de er i kopimodus.

La oss 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 spesielle tmux-kommandoer som tmux forstår når ruten er i kopimodus. Det er to varianter av kopikommandoen: copy-selectionog copy-pipe.

La oss omskrive Enternøkkelbinding med copy-pipe-kommando:

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

copy-pipekommando lagrer valgt tekst i tmux-buffer samme som copy-selection, pluss rør valgt tekst til gitt kommando pbcopy. Så vi får tekst lagret to steder: tmux-kopibufferen og systemutklippstavlen.

OSX. fest innpakning til brukernavn

Så langt så bra. Imidlertid, på noen versjoner av OSX, pbcopyog pbpaste fungerer ikke ordentlig når de kjøres under tmux.

Les mer informasjon fra Chris Johnsen om hvorfor det skjer:

tmux bruker biblioteksfunksjonen daemon (3) når serverprosessen startes. I Mac OS X 10.5 endret Apple daemon (3) for å flytte den resulterende prosessen fra sitt opprinnelige bootstrap-navneområde til root bootstrap-navneområdet. Dette betyr at tmux-serveren og dens barn automatisk og ukontrollabelt mister tilgangen til det som hadde vært deres opprinnelige bootstrap-navneområde (dvs. den som har tilgang til bordtjenesten).

En vanlig løsning er å bruke reattach-to-user-namespace wrapper. Dette gjør at vi kan starte en prosess og få den prosessen tilknyttet navneområdet for bootstrap per bruker, noe som får programmet til å oppføre seg slik vi forventer. Du må endre tastebinding riktig:

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

I tillegg må du fortelle tmux å kjøre skallet ditt (bash, zsh, ...) inne i en innpakning, ved å angi default-commandalternativet:

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

Merk : noen OSX-versjoner fungerer bra selv uten dette hacket (OSX 10.11.5 El Capitan), mens OSX Sierra-brukere rapporterer at dette hacket fortsatt er nødvendig.

Linux. Samhandle med X-valg via xclip og xsel

Vi kan bruke xclipeller xselkommandoer på Linux for å lagre tekst i utklippstavlen, det samme som pbcopypå OSX. På Linux er det flere typer utklippstavlevalg som vedlikeholdes av X-server: primær, sekundær og utklippstavle. Vi bekymrer oss bare for primær og utklippstavle. Secondary 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 bruker xsel:

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

Les her om sammenligning av xclipvs. xselhvis du er nysgjerrig. Ta også en titt på dette innlegget om xclipbruk og eksempler. Og ikke glem å installere et av disse verktøyene, da de kanskje ikke er en del av distribusjonen din.

Bruke ANSI OSC 52 escape-sekvens for å få terminalen til å lagre tekst i utklippstavlen

Så langt dekket vi bare lokale scenarier. Når du SSH til ekstern maskin, og starter tmux-økter der, kan du ikke benytte deg av pbcopy, xclipeller xselfordi tekst blir lagret i den eksterne maskinens utklippstavle, ikke i din lokale. Du trenger en måte å transportere kopiert tekst til utklippstavlen til din lokale maskin.

ANSI escape-sekvens er en sekvens av byte sendt til terminalen som er sammenflettet med vanlige utskrivbare tegn, og brukes til å kontrollere forskjellige terminalaspekter: for eksempel tekstfarger, markørposisjon, teksteffekter, tømningsskjerm. Terminalen er i stand til å oppdage en slik styrende sekvens av byte som får den til å utløse spesifikke handlinger og ikke skrive ut disse tegnene til utgangen.

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

Bruk tmux sett-utklippstavle i gnome-terminal (XTerm's disallowedWindowOps) - Spør Ubuntu - //askubuntu.com/questions/621522/use-tmux-set-clipboard-in-gnome-terminal-xterms-disallowedwindowops/621646