今回は、リモートサーバーにSSH接続したマシンとクライアント間のクリップボードを共有する時に詰まった部分があったので、まとめてみたいと思います。
やりたいことをざっくりとイメージ図にしました。
shell内でDISPLAYをVNC用に設定したアプリケーションApp. Aの中で作られた文字列をshellの方のクリップボードに格納したいということです。
環境について
Windows 10のクライアントPCにWSL1 (Penguin 20.04.1)を入れて、リモートサーバーにSSHしています。
XクライアントはX410を使っています。
接続先のリモートサーバーはCentOS 6.9です。
私は普段、リモートサーバー上ではターミナルを多重化してセッションを保持出来るtmux 2.3を使っています。
いずれのshellもzshを使っています。
Xのクリップボードにアクセスするコマンドとしてxsel 1.2を使用します。
基本的な使い方
本題に入る前に、基本的なVNCの使い方・xselの使い方を軽くまとめておきます。
本題は次のSectionです。
DISPLAYを指定してプログラムを起動する
X ServerのDISPLAYを指定してあるプログラムを開く時には
DISPLAY=:1.0 (shell command)
と記述します。このようにすることでVNC上のDISPLAYにXを出力できます。SSHでリモートサーバーにアクセスして、その上でXをローカルに飛ばすと通信で重くなるので、VNCで出力させた方が早いです。
クリップボードを共有する
Xはまた、クリップボードの管理機能も持っています。
ここでは、xselというパッケージを使用してクリップボードのコンテンツを読み書きします。
sshでログインする時に必ず-Xオプションをつけるか.ssh/configでForwardX11 yesをつけてX forwardingをしておかないとSSHした先とのクリップボード共有が出来ません。
Windows 10とWSLとのクリップボード連携はWindows用のX client (X410とかVcXsrv)をインストールしてWSLでの環境変数DISPLAY=:0.0を通しておけば大丈夫(なはず)です。
使い方を簡単に説明すると
echo "hoge" | xsel -bi
でクリップボードに"hoge"がコピーされ
xsel -bo
でクリップボードの中身を出力します。
本題
VNCで画面出力しているプログラム上でクリップボードを共有するには?
ここで、一つの問題が生じます。DISPLAYを指定して起動させたプログラム(App. A)では、当然DISPLAY変数が指定されたものになります(某政治家みたい)。
例えば以下のように、ただ$DISPLAYを出力するだけのshell scriptを書きます。
# !/bin/sh
echo $DISPLAY
これを普通に実行すると
% sh print_display.sh
localhost:10.0
と出力されますが、これをDISPLAYを指定して実行すると
% DISPLAY=:1.0 sh print_display.sh
:1.0
となります。これはこのshell script内ではDISPLAY変数が変更されて実行されていることになります。
これが、Xで管理されているクリップボードだとどうなるでしょうか。
% echo "from localhost" | xsel -bi # DISPLAYを指定しない(現在のDISPLAY)で"from localhost"を格納
% echo "from localhost to 1.0" | DISPLAY=:1.0 xsel -bi # DISPLAYを1.0にして、"from localhost to 1.0"を格納
% xsel -bo
from localhost # 現在のDISPLAYで動いているXのクリップボードを参照
% DISPLAY=:1.0 xsel -bo
from localhost to 1.0 # 1のDISPLAYで動いているXのクリップボードを参照
という結果になります。ではここで、VNCで動いているshellにてクリップボードの中身を参照してみます。
xsel -bo
:1.0
と、クリップボードの中身がVNC上で共有されています。
VNCにXを飛ばしているプログラム内で、今のshellのクリップボードを共有したい
では、イメージ図のApp. Aにてshellのクリップボードを利用する場合にはどうすれば良いでしょうか。
これの解はenvコマンドを使用して、このコマンドに対してのみshellのXを指定するということをします。
こちらを参考にさせていただきました。
このようなshell scriptを書いて実験します。
# !/bin/sh
echo "change display" | env DISPLAY=localhost:10.0 xsel -bi
env DISPLAY=localhost:10.0で、環境変数$DISPLAYをshellのXに変更して実行するようにしています。
このスクリプトを:1.0ポート(App Aを想定)で実行します。
% echo $DISPLAY
localhost:10.0 # 現在のDISPLAYはlocalhsot:10.0
% DISPLAY=:1.0 sh change_display_test.sh
% xsel -bo # 現在のXのクリップボードを見る
change display
大元のプログラムではDISPLAYを:1.0に指定して走らせていますが、そのプログラムの中で唯一クリップボードに格納する処理だけDISPLAYの指定を解除すれば、クリップボードがVNC DISPLAYではなくshellのものと共有出来るようになるわけです。
これでDISPLAYを変えて動かしたプログラムにて現在のXのクリップボードが参照できました。
tmux使いだともうひと手間
これで普通にSSHでログインしたshellでApp. Aを使う場合にはOKです。
しかし、ここでtmuxを使っている方はもうひと手間必要です。
sessionが持っているDISPLAY変数は起動したDISPLAY変数が保持される
tmuxで新たにsessionを立ち上げた時、このsessionのDISPLAY変数は破棄されるか途中で変更するまでずっと立ち上げた時のDISPLAY変数が使われます。
例えばlocalhost:10.0のshellでtmuxを立ち上げると、ずっとDISPLAY変数はlocalhost:10.0のままです。 デタッチして再アタッチしたshellがlocalhost:10.0でなくても 。
実験をします。まず、1つめのshellを作ります。この中で新たにtmuxのsessionを起動させ、デタッチします。
(client) % ssh (server)
(server) % echo $DISPLAY
localhost:10.0 # 起動したshellのDISPLAY変数
(server) % tmux
(server) % echo $DISPLAY
localhost:10.0 # tmuxのsessionが持っているDISPLAY変数
[detached (from session 1)] # デタッチさせます
ここで、localhost:10.0のshellを保持したまま新たにもう1回shellを作り、前のshellで作ってデタッチしたsessionにアタッチします。
(client) % ssh (server)
(server) % echo $DISPLAY
localhost:11.0 # 起動したshellのDISPLAY変数
(server) % tmux a -t 1 # 先程デタッチさせたsessionをアタッチ
(server) % echo $DISPLAY
localhost:10.0 # tmuxを起動した時のDISPLAY変数のまま
と、起動しているshellとtmux上のDISPLAY変数の不一致が起きます。この状態でlocalhost:10.0に対応する1つめのshellを終了させてtmux上で取得出来るでxselを動かそうとします。
(server) % exit
(server) % echo "hoge" | xsel -bi # DISPLAY変数が本当は死んでいるlocalhost:10.0なので、Errorになる
xsel: Can't open display: (null)
: Connection refused
と、存在しないDISPLAY変数を持ってこようとするので、Errorとなります。
回避策
本来であればアタッチしたshellのDISPLAY変数が欲しいですよね。それ、出力できます!!!
tmux showenvコマンドを使用します。このコマンドの引数にDISPLAYを指定すると、アタッチしたshellのDISPLAY変数を取得できます。
(server) % tmux showenv DISPLAY
DISPLAY=localhost:11.0
アタッチしたshellのDISPLAY変数を取ってこれました。あとはこれをenvコマンドと組み合わせるだけです。
最終形態
# !/bin/sh
echo "hoge" | env $(tmux showenv DISPLAY) xsel -bi
これを実行すると
(server) % DISPLAY=:1.0 sh change_display_test.sh
クライアント(WSL)の方に戻り、クリップボードの中身を確認すると
(client) % xsel -bo
hoge
と、狙った挙動になりました!
