今回は、リモートサーバーに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
と、狙った挙動になりました!