Jetson Nanoにリモートデスクトップ(VNC)環境を用意する

# この記事について
nVIDIAのJetson NanoにVNCサーバをインストールして、開発用ホストPC(Windows等)から接続できるようにします。
Jetson Nanoの環境立ち上げも含めて説明します。
(Jetson Nanoに限らず、通常のLinuxでも同じ方法で大丈夫だと思います。)

## なぜ必要?
Jetson NanoはUbuntuが起動して、パソコン同等の環境が動きます。
とはいえ、以下のような思いがあります。

- 実際のコーディングなどは使い慣れたホストPC(Windows/PC Linux等)でやりたい
- Jetson Nanoにキーボード、マウス、ディスプレイを接続するのが面倒、不可能 (お金的に、スペース的に)


### コマンド操作だけなら
もしもやりたいことがコマンド操作だけなら、リモートデスクトップは不要です。
ホストPCでコーディングして、コンパイル・実行だけをJetson Nanoでやるなら、ssh接続さえできれば大丈夫です。実際にはコーディング用のエディタ(VSCode等)に、SSH用プラグインを入れることで、かなりやりやすくなります。 こちらの記事もご参考ください。 [ラズパイ用のC/C++開発環境をお手軽に構築する](https://qiita.com/take-iwiw/items/46119bb7d41c6030d34f)

ただし、その場合でもたまにGUIからの操作が必要だったり、画面出力を確認したい場合があると思います。そのような時に、いちいちHDMI接続を切り替えるのは面倒です。


# 環境
- ホストPC
	- Windows 10 (64-bit)
	- Git Bash (sshコマンドが使えれば何でもいい。MSYS,Tera TermでもOK)
	- RealVNC VNC Viewer (他のVNCクライアントでも大丈夫)
- ターゲット
	- Jetson Nano
	- jetson-nano-sd-r32.1-2019-03-18

# 一番最初の起動～SSH接続
https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit 通りに進めていけば、イメージ書き込みから初期設定まで完了します。
一番最初の設定と、JetsonNanoのIPアドレスを確認するまでは、Jetson Nanoにキーボード、マウス、ディスプレイを接続する必要があります。

- イメージを書き込んだSDカードを差し込み、電源ONします。ユーザ名やパスワード、タイムゾーンなどUbuntu OSの初期設定をします。
- Ubuntuが起動したらDashボタンをクリックし、`terminal` と入力し、Terminalを起動します。
- Terminal上で、`ifconfig` と入力し、Jetson NanoのIPアドレスを確認します。以後、192.168.1.115とします。

ここまで出来たら、もうJetson Nano本体を直接操作する必要はありません。キーボードやHDMIケーブルは外しても大丈夫です。

## ホストPCからSSH接続する
ホストPC上のsshが使用可能な端末(例. Git Bash)から、`ssh takeshi@192.168.1.115` と入力します。(ユーザ名、IPアドレスは適宜置き換えてください)
パスワードを入力すれば、SSH接続ができるはずです。


# リモートデスクトップ(VNC)環境
## 方針
単にVNCサーバを立ち上げると、本体の画面(HDMI出力)とは別のXが起動されます。組込み環境ではできるだけ負荷を低減させたいのと、そのX用にも別途デスクトップ環境(GNOME等)を用意する必要があり面倒です。(少し試したのですが、アプリケーションの画面がうまく飛んでくれなかったりと、結構ハマりそうでした。)
そのため、本体の画面(HDMI出力)のXをそのまま使用するようにします。(Raspberry Pi標準のVNCと同じ感じにします)

## 追記
Ubuntuには標準でDesktop Sharing(デスクトップ共有) 機能があります。これを使えば、上記のことはできます。
ただし、2019年4～5月時点のOSイメージだと、設定しようとしたらCrashしてしまうという問題がありました。

もともと本記事は、このDesktop Sharingを使用しないで、TigerVNCを使ってVNC接続する方法を記載していました。
ただ、標準のDesktop Sharingを使えるようにする方法が見つかったので、その内容も併せて記載します。お好きな方をご使用ください。

**後述の「方法2」の方がおすすめです!**

# (方法1) TigerVNCを使う
## VNC Serverのインストールと設定
VNCサーバとして、TigerVNCを使用します。
以下コマンドで、インストール、初期設定をします。最後に起動してみます。

```sh:SSHターミナル(VNCインストールと設定と手動起動)
sudo apt update
sudo apt install tigervnc-common tigervnc-standalone-server tigervnc-scraping-server
vncpasswd

# Would you like to enter a view-only password (y/n)? と聞かれるが、nでよい。

x0vncserver -display :0 -passwordfile ~/.vnc/passwd
# 別途、X環境を作る場合は、vncserver :1 などにする
```

## Windowsから接続してみる
Windows上でVNC Viewerを起動します。
File -> New connection から新しいサーバ設定を追加します。
`VNC Server:` の所に、ssh接続にも使用したJetson NanoのIPアドレスを設定します。(通常、IPアドレスの最後にコロンとディスプレイ番号を付けます。今回は`:0`ですが省略可能です)

作成したサーバ設定をダブルクリックして接続します。パスワードは、JetsonNanoのUbuntuパスワードではなく、vncpasswdで設定したパスワードを入力します。

成功したら、Jetson Nanoの画面が表示されるはずです。
関連する画面のスクリーンショットを張っておきます。左上から時計回りに、SSHターミナル、VNC Viewer設定、リモート画面、です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214268/df04dbc0-6537-52df-e784-e062f3e8eb61.png)


接続成功したら、一度VNC Viewerは閉じてください。また、SSHターミナル上でCtrl-Cして、x0vncserverを停止します。


## VNC Serverをデーモンとして自動起動させる
VNCサーバを毎回手動で起動するのは面倒なので、システム起動時に自動的にVNCサーバが起動するようにします。
以下コマンドで、systemdのサービス用設定ファイルを作成します。

```sh:SSHターミナル
sudo vi /etc/systemd/system/x0vncserver.service
```

以下のような内容を入力(コピペ)します。
ユーザ名やパスは適宜変更してください。(`~/`などは使用できませんでした)


```txt:/etc/systemd/system/x0vncserver.service
[Unit]
Description=Remote desktop service (VNC)
After=syslog.target
After=network.target remote-fs.target nss-lookup.target
After=x11-common.service 

[Service]
Type=forking
User=takeshi
Group=takeshi
WorkingDirectory=/home/takeshi
ExecStart=/bin/sh -c 'sleep 10 && /usr/bin/x0vncserver -display :0  -rfbport 5900 -passwordfile /home/takeshi/.vnc/passwd &'

[Install]
WantedBy=multi-user.target
```

上記スクリプトで設定した内容が起動時に呼ばれるのですが、まずは手動で呼び出して正しく動くか確認します。

```sh:SSHターミナル(Systemdサービス起動確認)
sudo systemctl start x0vncserver.service
sudo systemctl status x0vncserver.service

# 正しく動いていたら、以下のように表示されます。
## x0vncserver.service - Remote desktop service (VNC)
##   Loaded: loaded (/etc/systemd/system/x0vncserver.service; disabled; vendor preset: enabled)
##   Active: active (running) since Tue 2019-04-30 00:10:57 JST; 51ms ago
##  Process: 15752 ExecStart=/bin/sh -c sleep 10 && /usr/bin/x0vncserver -display :0  -rfbport 5900 -passwordfile /home/takeshi/.vnc/passwd & (code=exite
## Main PID: 15753 (sh)
##    Tasks: 2 (limit: 4189)
##   CGroup: /system.slice/x0vncserver.service
##           ├─15753 /bin/sh -c sleep 10 && /usr/bin/x0vncserver -display :0  -rfbport 5900 -passwordfile /home/takeshi/.vnc/passwd &
##           └─15755 sleep 10
##
## 4月 30 00:10:57 takeshi-jetson systemd[1]: Starting Remote desktop service (VNC)...
## 4月 30 00:10:57 takeshi-jetson systemd[1]: Started Remote desktop service (VNC).

```

OKなら、以下コマンドでサービスを有効化して再起動します。


```sh:SSHターミナル(Systemdサービス有効化)
sudo systemctl enable x0vncserver.service 
sudo reboot yes


# 再起動後、ちゃんとサービスが起動しているか確認
systemctl list-units | grep vnc
# 正しく動いていたら、以下のように表示されます。
## x0vncserver.service                                                                         loaded active running   Remote desktop service (VNC)  
```

これ以降は何もしないでもVNC接続ができるようになります。

## メモ
### Systemd設定ファイルに関して
`x0vncserver` の起動は、X関係の起動を待つ必要があります。待たないと起動に失敗します。
一応、`After=x11-common.service ` として、それっぽいことはしているのですが、うまく行きませんでした。
しょうがないので、`sleep 10` するという格好悪い回避策を取りました。
もっといい方法があれば、どなたか教えていただきたいです。

### 既定のVNCサービスの無効化
`systemctl list-units | grep vnc` を入力したときに、既にdisplay1 (`:1`)用のSystemdサービスが登録されている可能性があります。その場合は、`sudo systemctl disable vncserver@display:1.service`などで無効化しておきます。



### クリップボードのサポート
リモート-サーバ間でのクリップボードの共有は未サポートの模様

x0vncserver does not support clipboard
https://github.com/TigerVNC/tigervnc/issues/529


### オーバーヘッド
x0vncserverのCPU使用率は、画面に動きがない場合で、2%/400%程度。画面が激しく動いている場合で、40%/400%。
なので、処理時間測定したり、ガチで何かを動かす場合にはリモートデスクトップはOFFにした方が良い


# (方法2) Desktop Sharing(Vino)を使う
https://blog.hackster.io/getting-started-with-the-nvidia-jetson-nano-developer-kit-43aa7c298797
の記事の「Enabling Desktop Sharing」　の手順通りです。

基本的には、GNOMEの設定を修正しているだけなので、将来的には何もしないでもOKになると思っています。そうなればこの記事も不要になります。

また、「コンソールからログイン中じゃないとVNC接続できないので、 xrdpをインストールする」みたいなことが書いてありましたが、僕の環境では不要でした。ログイン設定で、Automatically login を有効にしているからかもしれません。(逆に言うと、ログイン画面も出ないので危険ではありますが。)

## 必要なコマンドと操作抜粋
```sh:編集とコンパイルコマンド
sudo nano /usr/share/glib-2.0/schemas/org.gnome.Vino.gschema.xml
sudo glib-compile-schemas /usr/share/glib-2.0/schemas
gsettings set org.gnome.Vino require-encryption false
gsettings set org.gnome.Vino prompt-enabled false
```

```xml:/usr/share/glib-2.0/schemas/org.gnome.Vino.gschema.xml
    <key name='enabled' type='b'>
      <summary>Enable remote access to the desktop</summary>
        <description>
          If true, allows remote access to the desktop via the RFB
          protocol. Users on remote machines may then connect to the
          desktop using a VNC viewer.
        </description>
      <default>false</default>
    </key>
```

- 設定画面からDesktop Sharingを有効にする
- 自動起動設定
	- `Startup Applications Preferences` に以下コマンドを追加
	- `/usr/lib/vino/vino-server`


# メモ
## アプリケーションの画面だけを飛ばすなら
画面全体ではなくて、アプリケーションのウィンドウだけを見たいなら、X11 Frowardingの方が便利かもしれません。ただ、飛んだり飛ばなかったりします。OpenCVでの画面表示くらいなら大丈夫でした。
https://qiita.com/take-iwiw/items/e52ec08e8c25634a57d7#x11-forwardingでアプリケーション画面を転送する

## 解像度が低い場合
HDMIをディスプレイに接続していない状態でVNC接続すると、解像度が640x480 になってしまいます。
恐らく、HDMI接続されていないときはフレームバッファが確保されないのではないかと思います。
解決策を https://devtalk.nvidia.com/default/topic/995621/jetson-tx1/jetson-tx1-desktop-sharing-resolution-problem-without-real-monitor/ で見つけました。
`/etc/X11/xorg.conf` ファイルを以下のように編集します (`sudo nano /etc/X11/xorg.conf` )。

```txt:/etc/X11/xorg.conf
# Copyright (c) 2011-2013 NVIDIA CORPORATION.  All Rights Reserved.

#
# This is the minimal configuration necessary to use the Tegra driver.
# Please refer to the xorg.conf man page for more configuration
# options provided by the X server, including display-related options
# provided by RandR 1.2 and higher.

# Disable extensions not useful on Tegra.
Section "Module"
    Disable     "dri"
    SubSection  "extmod"
        Option  "omit xfree86-dga"
    EndSubSection
EndSection

Section "Device"
    Identifier  "Tegra0"
    Driver      "nvidia"
# Allow X server to be started even if no display devices are connected.
    Option      "AllowEmptyInitialConfiguration" "true"
EndSection


Section "Monitor"
   Identifier "DSI-0"
   Option    "Ignore"
EndSection

Section "Screen"
   Identifier    "Default Screen"
   Monitor        "Configured Monitor"
   Device        "Default Device"
   SubSection "Display"
       Depth    24
       Virtual 1280 800
   EndSubSection
EndSection
```




# 参考
https://wiki.archlinux.jp/index.php/TigerVNC#systemd_.E3.81.A7_x0vncserver_.E3.82.92.E8.B5.B7.E5.8B.95.E3.83.BB.E5.81.9C.E6.AD.A2
https://dev.classmethod.jp/cloud/aws/service-control-use-systemd/
https://qiita.com/Tats_U_/items/c170f61a5e03ae045128
https://devtalk.nvidia.com/default/topic/995621/jetson-tx1/jetson-tx1-desktop-sharing-resolution-problem-without-real-monitor/