Qt を使った Auto Grade Linux アプリ開発方法


Qt 初心者が Qt を使った AGL アプリを作成する手順の記録。Linux GUI に慣れてないので、Qt アプリの作成を Mac OS で行い、クロスビルドから wgt の作成までを Ubuntu 16.04 LTS を使った。

ソースコード: https://github.com/propella/agl-hello

## Qt インストール方法

* https://www.qt.io/download-open-source/#section-2 のインストーラで ~/Qt にインストール

## 簡単なアプリの作成

### 簡単なアプリのソース

自分が思いついた一番簡単な Qt の GUI のプロジェクトファイルは以下のようになった。

```
# agl-hello.pro

QT += widgets
TARGET = agl-hello
TEMPLATE = app
SOURCES += main.cpp
CONFIG += debug
```

pro ファイルは qmake というツールの設定ファイルで、qmake は pro ファイルから Makefile 等を生成する。代入形式で定義する。リストは空白区切り。

* [QT](http://doc.qt.io/qt-5/qmake-variable-reference.html#qt) : プログラムで使う [Qt Module](http://doc.qt.io/qt-5/qtmodules.html)
    * QT にはデフォルトで `core` や `gui` 等の基本的な値がセットされているため、追加するには `+=` を使う。
* [TARGET](http://doc.qt.io/qt-5/qmake-variable-reference.html#target) : 出来上がるファイルの名前。デフォルトで project name と同じになる。
* [TEMPLATE](http://doc.qt.io/qt-5/qmake-variable-reference.html#template) : アプリを作る時は app, ライブラリの時は lib
* [SOURCES](http://doc.qt.io/qt-5/qmake-variable-reference.html#sources) : ソースコードを記述する。
* [CONFIG](http://doc.qt.io/qt-5/qmake-variable-reference.html#config) : デバッグしたい時 debug を追加
* pro ファイルのデバッグには [message](http://doc.qt.io/qt-5/qmake-test-function-reference.html#message-string) が便利。

以下プログラムソースコード。

```c
// main.cpp

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QLabel label("Hello, World!");
    label.setWindowFlags(Qt::FramelessWindowHint);
    label.show();
    return a.exec();
}
```

ここで `setWindowFlags(Qt::FramelessWindowHint)` で FramelessWindowHint を指定するのがポイント。これが無いとなぜか AGL HomeScreen から表示出来なかった。

### ビルド方法

Qt Creator を使う場合

* File > Open File or Project > agl-hello.pro を選択
* Configure Project でデフォルトのまま Configure Project を押す。いつの間にか qthello.pro.user が出来ている。
* 再生ボタンで実行
* `../build-qthello-Desktop_Qt_5_9_1_clang_64bit-Debug` にビルド用フォルダが出来ている。

Terminal を使う場合

    ~/Qt/5.9.1/clang_64/bin/qmake
    make

## クロスビルド用 Docker 環境の作成

[AGL SDK Quick Setup](http://docs.automotivelinux.org/docs/getting_started/en/dev/reference/setup-sdk-environment.html) に、Docker を使ったクロスビルド環境の作成が紹介されている。Docker 無しで Ubuntu 16.04 の上に直接 SDK をインストールしても動くので、面倒ならこのステップは省略出来る。

この docker image は特定のディレクトリ構成で使うように出来ているので、~/ssd 及び ~/devel を作成する

    mkdir ~/ssd ~/devel
    chmod a+w ~/ssd ~/devel

すると以下のようにコンテナ内からホストにアクセス出来る。

| container | host | 用途 |
| --------- | ---- | ------- |
| `/xdt`      |  `~/ssd/xdt_$ID` | SDK インストール場所 |
| `/home/devel/mirror` | `~/ssd/localmirror_$ID` | 作業場所 |
| `/home/devel/share` | `~/devel/docker/share` | ダウンロードファイル置き場 |

docker image をダウンロードして設定

    wget https://download.automotivelinux.org/AGL/snapshots/sdk/docker/docker_agl_worker-3.0.tar.xz
    sudo docker load -i docker_agl_worker-3.0.tar.xz
    docker images
    git clone https://git.automotivelinux.org/AGL/docker-worker-generator
    cd docker-worker-generator
    ./contrib/create_container 0 docker.automotivelinux.org/agl/worker:3.0

コンテナにログイン (パスワードは devel) なぜか TERM=screen が無いと、screen 経由で vi が上手く動かない。

    TERM=screen ssh -p 2222 devel@localhost

コンテナ内からは uid: 1664 / gid: 1664 の devel というユーザでホストとファイル共有する。以下のように設定するとホストとファイル交換が出来る。(linux 詳しく無いので間違ってるかも)

ホスト

    sudo groupadd --gid 1664 devel
    sudo useradd --uid 1664 --gid 1664 devel

コンテナ

    sudo groupadd --gid 1002 tyamamiya
    sudo gpasswd -a devel tyamamiya

## SDK のインストール

AGL のターゲット向け SDK はどこからか拾ってくるか、自分で作る場合は AGL の build ディレクトリで agl-demo-platform-crosssdk をビルドする。

    bitbake agl-demo-platform-crosssdk

SDK は tmp/deploy/sdk/poky-agl-glibc-x86_64-agl-demo-platform-crosssdk-aarch64-toolchain-3.99.1+snapshot.sh のような場所に出来る。Docker を使っている場合はこれを ~/devel/docker/share に置いてコンテナと共有し、コンテナの `install_sdk` でインストールする。

Docker を使わない場合はこのファイルを直接実行すればインストーラが起動する。

host

    mv tmp/deploy/sdk/poky-agl-glibc-x86_64-agl-demo-platform-crosssdk-aarch64-toolchain-3.99.1+snapshot.sh ~/devel/docker/share/

container

    install_sdk ~/share/poky-agl-glibc-x86_64-agl-demo-platform-crosssdk-aarch64-toolchain-3.99.1+snapshot.sh 

実機で動くかテストしてみる。(デバイスのホスト名が `device` とする)

    $ source /xdt/sdk/environment-setup-aarch64-agl-linux
    $ cat <<EOT > hello.c
    #include <stdio.h>
    int main() { printf("Hello world\n"); return 0; }
    EOT
    $ $CC -o hello hello.c
    $ scp hello root@device:.
    $ ssh root@device ./hello
    Hello world

## アプリを Widget にする

### クロスコンパイルしてデバイス上に表示する

サンプルアプリをコンテナの ~/mirror にコピーする。SDK を source すると qmake が使えるようになるので、そのままコンテナでビルドし、デバイス上で動く事を確認する。

    $ git clone git@github.com:propella/agl-hello.git
    $ cd agl-hello
    $ source /xdt/sdk/environment-setup-aarch64-agl-linux
    $ qmake
    $ make
    $ scp agl-hello root@device:.

AGL 起動時に Home Screen が表示される場合、そのままでは普通の Qt アプリが動かないので weston.ini を編集して ivi-shell.so を無効にする。

    $ vi /etc/xdg/weston/weston.ini
    
    # shell=ivi-shell.so (shell=ivi-shell.so をコメントアウト)
    
    $ systemctl restart weston
    $ ~/agl-hello

AGL デバイスの画面に寂しく Hello World! が表示されれば成功。終わったら weston.ini の設定を戻して `systemctl restart weston` する。

### Wiget を作成する。

AGL のアプリは [W3C Widget](https://www.w3.org/TR/widgets/) 仕様に基づいて作成した wgt ファイルだ。SDK 内に wgt ファイルを作るためのツールがある。パッケージを作るためには以下のファイルが必要。

* [config.xml](http://docs.automotivelinux.org/docs/apis_services/en/dev/reference/af-main/2.2-config.xml.html) : 今から作ります
* アイコンファイル : なんでも良い
* 実行ファイル

```xml
<?xml version="1.0" encoding="UTF-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" id="poi" version="0.1">
  <name>Hello World</name>
  <icon src="icon.png"/>
  <content src="agl-hello" type="application/x-executable"/>
  <description>A Simple Hello World</description>
  <author>Takashi Yamamiya</author>
  <feature name="urn:AGL:widget:required-permission">
    <param name="http://tizen.org/privilege/internal/dbus" value="required" />
  </feature>
  <license>MIT</license>
</widget>
```

AGL App Framework は widget の id 属性でアプリを識別します。今回何故か `poi` という ID にしてみました。本来 ID はアプリを表す名前であるべきですが、現時点 (AGL 3.0) では HomeScreen の実装が不十分で、プリインストールされた決め打ちの ID のアイコンしか表示されません。今回は問題のある HomeScreen を騙すために、`poi` という既に存在するアプリの ID を流用します。

これらをまとめて適当なディレクトリに置きます。SDK の wgtpkg-pack コマンドで wgt ファイルを作成し、デバイスに転送します。

```
mkdir root
cp config.xml root/
cp icon.png root/
cp agl-hello root/
wgtpkg-pack -f -o agl-hello.wgt root
scp agl-hello.wgt root@device:.
```

デバイス側では afm-util コマンドを使ってアプリの登録削除を行います。

```
afm-util uninstall poi@0.1 # 既存の poi アプリを削除
afm-util install agl-hello.wgt # 作成した Hello World を poi 名前で登録
```

これで AGL Home Screen から POINT OF INTEREST のアイコンを押すと Hello World が表示されます。起動中にアプリの入れ替えは出来ないようなので、うまくいかない時はリブートして下さい。

afm-util には便利な機能が沢山あって [Quick Tutorial](http://docs.automotivelinux.org/docs/apis_services/en/dev/reference/af-main/4-quick-tutorial.html) に詳しい紹介があります。私がよく使うのは以下です。

* afm-util uninstall (id@バージョン) # 既存の poi アプリを削除
* afm-util install (wgt ファイル) # 作成した Hello World を poi 名前で登録
* afm-util runners # 起動中のアプリを確認
* afm-util terminate (runid) # runid で起動しているアプリを停止
* systemctl --user restart HomeScreen # HomeScreen 再起動
    * アプリの入れ替えには役に立たないが、インストールしていない状態がからインストールした時にこのコマンドでアイコンを表示し直せる。

## CMake で Widget ファイルを作成する

折角なのでこれらの作業を自動化するために cmake を使う。https://git.automotivelinux.org/apps/hvac 等の他のアプリを見ると qmake だけでも自動化出来るようだが、たまたま参考にした [poi](https://github.com/AGLExport/genivi-navi-yelp-client/blob/agl/CMakeLists.txt) が cmake を使っていたので cmake になってしまった。


```cmake
# CMakeLists.txt

cmake_minimum_required(VERSION 2.8.11)

project(agl-hello)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC ")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)

find_package(Qt5Widgets REQUIRED)
qt5_add_resources(agl_hello_QRC )
add_executable(agl-hello main.cpp ${agl_hello_SRC} ${agl_hello_QRC})
target_link_libraries(agl-hello Qt5::Core Qt5::Widgets)

install (TARGETS agl-hello DESTINATION bin)

configure_file(config.xml.in config.xml)
add_custom_command(
        OUTPUT agl-hello.wgt
        DEPENDS agl-hello
        COMMAND rm -rf package
        COMMAND mkdir -p package/root
        COMMAND cp config.xml package/root/
        COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/icon.png package/root/icon.png
        COMMAND cp agl-hello package/root/agl-hello
        COMMAND wgtpkg-pack -f -o package/agl-hello.wgt package/root
)
add_custom_target(widget ALL DEPENDS agl-hello.wgt)
```

qmake から cmake への変換は http://doc.qt.io/qt-5/cmake-manual.html に詳しく書いてあるが、ポイントは以下

* set(CMAKE_INCLUDE_CURRENT_DIR ON) # 良くわからないがとりあえず入れておく。
* set(CMAKE_AUTOMOC ON) # 良くわからないがとりあえず入れておく。
* find_package(Qt5Widgets REQUIRED) # qt5_ 関連のマクロが使えるようになる。
* qt5_add_resources(agl_hello_QRC ) # この例では使ってないが、リソースファイルをビルドしたい時に使う。
* target_link_libraries(agl-hello Qt5::Core Qt5::Widgets) # cmake の QT と同様の内容を記述

これで結局ビルド手順は以下のようになる。

```
cmake
make
```

package/agl-hello.wgt に Widget が出来る。

## AGL Recipe の書き方

ここまで行くとあとはレシピを書くだけだが、力尽きたので今度書く。

基本 [cmake_qt5](https://github.com/meta-qt5/meta-qt5/blob/master/classes/qmake5.bbclass), [pkgconfig](https://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/meta/classes/pkgconfig.bbclass), [aglwgt](https://git.automotivelinux.org/AGL/meta-agl/tree/meta-app-framework/classes/aglwgt.bbclass) を inherit するだけでパッケージのインストールまで行われる。