tmux на практика: интеграция със системния клипборд

Как да изградите мост между буфера за копиране на tmux и системния клипборд и да съхранявате избран текст в системния клипборд на OSX или Linux по начин, който адресира както локални, така и отдалечени сценарии на използване

Това е 4-тата част от моя tmux в поредицата статии на практика.

В предишната част от поредицата „tmux на практика“ говорихме за неща като буфер за превъртане, режим на копиране и леко засегнахме темата за копирането на текст в буфера за копиране на tmux.

Рано или късно ще разберете, че каквото и да копирате в tmux, се съхранява само в буфера за копиране на tmux, но не се споделя със системния клипборд. Копирането и поставянето са толкова често срещани операции, че самото това ограничение е достатъчно, за да превърне tmux в безполезна тухла, въпреки други екстри.

В тази публикация ще проучим как да изградим мост между буфера за копиране на tmux и системния клипборд, за да съхраняваме копиран текст в системния клипборд, по начин, който адресира както локални, така и отдалечени сценарии на използване.

Ще обсъдим следните техники:

  1. Само за OSX, споделяйте текст с клипборда, използвайки “pbcopy”
  2. Само за OSX, използвайки обвивка „reattach-to-user-namespace“, за да накара pbcopy да работи правилно в tmux среда
  3. Само за Linux, споделяйте текст с X селекция с помощта на xclipили xselкоманди

Техниките по-горе се отнасят само до локални сценарии.

За поддържане на отдалечени сценарии има 2 допълнителни метода:

  1. Използвайте изходната последователност ANSI OSC 52, за да говорите с контролен / родителски терминал за управление и съхраняване на текст в клипборда на локална машина.
  2. Настройте слушател на локална мрежа, който подава вход към pbcopyили xclipили xsel. Пайп копира избран текст от отдалечена машина на слушател на локалната машина чрез SSH отдалечено тунелиране. Това е по-скоро свързано и ще отделя специална публикация, за да го опиша.

OSX. команди pbcopy и pbpaste

pbcopyи pbpasteкомандите ви позволяват да взаимодействате и манипулирате системния клипборд от командния ред.

pbcopy чете данни от stdinи ги съхранява в клипборда. pbpasteправи обратното и поставя копиран текст stdout.

Идеята е да се включите в различни команди tmux, които успяват да копират текст, докато са в режим на копиране.

Нека ги изброим:

$ 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-cancelи copy-end-of-lineса специални tmux команди, които tmux разбират, когато панелът е в режим на копиране. Има два вкуса на командата за копиране: copy-selectionи copy-pipe.

Нека пренапишем Enterсвързването на клавиши с команда copy-pipe:

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

copy-pipeкоманда съхранява избрания текст в буфера на tmux, който е същият copy-selection, плюс добавя избрания текст към дадената команда pbcopy. Така получаваме текст, съхраняван на две места: буферът за копиране на tmux и системният клипборд.

OSX. обвивка reattach-to-user-namespace

Дотук добре. Въпреки това, при някои версии на OSX pbcopyи pbpaste не успяват да функционират правилно, когато се изпълняват под tmux.

Прочетете повече подробности от Крис Джонсън за това защо се случва:

tmux използва функцията библиотека на демона (3), когато стартира процеса на сървъра си. В Mac OS X 10.5 Apple смени демона (3), за да премести резултата от първоначалното пространство за имена на bootstrap в пространството на имената на root bootstrap. Това означава, че tmux сървърът и неговите деца автоматично и неконтролируемо ще загубят достъп до това, което би било първоначалното им пространство на имена на bootstrap (т.е. това, което има достъп до картонената услуга).

Честото решение е да се използва обвивка reattach-to-user-namespace. Това ни позволява да стартираме процес и този процес да бъде прикрепен към пространството от имена на bootstrap за всеки потребител, което кара програмата да се държи както очакваме. Трябва да промените правилно свързването на клавишите:

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

Освен това ще трябва да кажете на tmux да стартира вашата черупка (bash, zsh, ...) в обвивка, като зададете default-commandопция:

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

Забележка : някои версии на OSX работят добре дори без този хак (OSX 10.11.5 El Capitan), докато потребителите на OSX Sierra съобщават, че този хак все още е необходим.

Linux. Взаимодействайте с X селекцията чрез xclip и xsel

Можем да използваме xclipили xselкоманди на Linux за съхраняване на текст в клипборда, както pbcopyпри OSX. В Linux има няколко вида избор на клипборд, поддържани от X сървър: първичен, вторичен и клипборд. Ние се занимаваме само с първичен и клипборд. Вторичното е замислено като алтернатива на основното.

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

Или когато използвате xsel:

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

Прочетете тук за сравнение на xclipсрещу xsel, ако сте любопитни. Също така вижте тази публикация относно xclipупотребата и примерите. И не забравяйте да инсталирате една от тези помощни програми, тъй като те може да не са част от вашата дистрибуция.

Използване на изходна последователност ANSI OSC 52, за да накара терминалът да съхранява текст в клипборда

Досега обхващахме само местни сценарии. Когато SSH към отдалечена машина, и да започне tmux сесии там, не можете да се възползвате от pbcopy, xclipили xsel, тъй като текст ще се съхранява в клипборда на отдалечената машина, а не в местна един. Имате нужда от някакъв начин за транспортиране на копиран текст в клипборда на вашата локална машина.

Ескейп последователността на ANSI е последователност от байтове, изпратени до терминала, които се преплитат с редовни символи за печат и се използват за управление на различни аспекти на терминала: като цветове на текста, позиция на курсора, текстови ефекти, изчистващ екран. Терминалът е способен да открие такава контролна последователност от байтове, която го кара да задейства конкретни действия и да не отпечатва тези символи на изхода.

ESCЕскейп последователността на ANSI може да бъде открита, когато започват с ASCII символ (0x1b шестнадесетичен, 027 десетичен, \ 033 в осмица). Например, когато терминалът види \033[2Aпоследователността, той ще премести позицията на курсора на 2 линии нагоре.

Наистина има много от тези известни последователности. Някои от тях са еднакви за различните типове терминали, докато други могат да варират и да са много специфични за вашия терминален емулатор. Използвайте infocmpкоманда за заявка към terminfoбаза данни за изходни последователности, поддържани от различни видове терминали.

Добре, чудесно, но как може да ни помогне по отношение на клипборда? Оказва се, че има специална категория евакуационни последователности: „Контроли на операционната система“ (OSC) и „OSC 52“ евакуационна последователност, която позволява на приложенията да взаимодействат с клипборда.

Ако използвате iTerm, опитайте да изпълните следната команда и след това “ ⌘V”, за да видите съдържанието на системния клипборд. Уверете се, че сте включили OSC 52 манипулиране на последователността за бягство: „Предпочитания -> Общи -> Приложенията в терминала могат да имат достъп до клипборда“.

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

Изводът е, че можем да съхраняваме текст в системния клипборд, като изпращаме специално изработена ANSI изходна последователност до нашия терминал.

Нека напишем скрипта на черупката 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"

И така, четем текст, от който да копираме stdin, след което проверяваме дали дължината му надвишава максималната дължина от 74994 байта. Ако е вярно, ние го изрязваме и накрая преобразуваме данните в base64 и обвиваме в OSC 52 escape последователност:\033]53;c;${data_in_base64}\a

Тогава нека го свържем с нашите клавишни връзки tmux. Това е доста лесно: просто насочете избрания текст към нашия yank.shскрипт, точно както го пренасяме към pbcopyили xclip.

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

Остава обаче едно парче за завършване на пъзела. Къде трябва да изпратим евакуационната последователност? Очевидно само изпращането му до stdoutняма да работи. Целта трябва да бъде нашият емулатор на родителски терминал, но ние не знаем правилно tty. И така, ще го изпратим в активния прозорец на ttytmux и ще кажем на tmux да го изпрати отново на емулатора на родителския терминал:

# 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"

Използваме tmux list-panesкоманда за заявка за активния прозорец и това е tty. Ние също така поставяме нашата OSC 52 последователност в допълнителна екранираща последователност на обвивката (Device Control String, ESC P), така че tmux разгръща този плик и предава OSC 52 на родителския терминал.

В по-новите версии на tmux можете да кажете на tmux да обработва взаимодействията с клипборда вместо вас. Вижте set-clipboardопцията tmux. on - tmux ще създаде вътрешен буфер и ще се опита да настрои клипборда на терминала с помощта на OSC 52. external - не създава буфер, но все пак се опитва да настрои терминала.

Просто се уверете, че е externalили on:

set -g set-clipboard on

И така, ако tmux вече е способен на тази функция, защо трябва да се занимаваме с ръчно свързване на OSC 52 неща? Това е така, защото set-clipboardне работи, когато имате отдалечена tmux сесия, вложена в локална. И тя работи само в тези терминали, които поддържат OSC 52 обработка на аварийната последователност.

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