D-Bus と QtDBus (その2：CLIから叩く)

# TL;DR

* QtDBus で書いたアプリと CLI から通信してみよう
* dbus-send で API を叩く
* dbus-monitor で監視してみる

# はじめに
[前回](https://qiita.com/tkhshmsy/items/68842ebb1518a6e99d32)書いたサンプルの dbusserver/dbusclient について、
テストやデバッグのときは外からアクセス・監視ができると便利です。CLI から叩いてみましょう。

# CLIから D-Bus を叩く

dbus-send コマンドを使います。
まず dbusserver に定義・設定した D-Bus 上の固有情報をメモっておきます。
```cpp
    Q_CLASSINFO("D-Bus Service", "local.myhost")
    Q_CLASSINFO("D-Bus Path", "/QtDBusDemo/DBusServer")
    Q_CLASSINFO("D-Bus Interface", "api.dbusserver")
```

## サーバーの動作確認

まず D-Bus 上にサービスが登録されているのかを確認します。
D-Bus 上の共通インターフェースから情報を取ってみます。
```bash
$ dbus-send --session --print-reply  --type=method_call --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames
method return time=1662279963.823035 sender=org.freedesktop.DBus -> destination=:1.171 serial=3 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string "org.freedesktop.Notifications"
      ...
      string "local.myhost"
      ...
   ]
```

local.myhost がいるのでサービスは登録されているみたいですね。
次に Path と Interface がどう認識されているか。local.myhost の "/" の情報をとってみます。
```bash
$ dbus-send --session --print-reply --type=method_call --dest=local.myhost / org.freedesktop.DBus.Introspectable.Introspect
method return time=1662280010.175647 sender=:1.170 -> destination=:1.172 serial=9 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="xml_data" type="s" direction="out"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus.Peer">
  ...
  </interface>
  <node name="QtDBusDemo"/>
</node>
"
```

QtDBusDemo という node が見えているのでそこをもう一度。
```bash
$ dbus-send --session --dest=local.myhost --type=method_call --print-reply /QtDBusDemo org.freedesktop.DBus.Introspectable.Introspect
method return time=1662280043.904308 sender=:1.170 -> destination=:1.173 serial=10 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="xml_data" type="s" direction="out"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus.Peer">
  ...
  </interface>
  <node name="DBusServer"/>
</node>
"
```

さらに DBusService が見えているのでさらにもう一度。
```bash
$ dbus-send --session --dest=local.myhost --type=method_call --print-reply /QtDBusDemo/DBusServer org.freedesktop.DBus.Introspectable.Introspect
method return time=1662280080.064612 sender=:1.170 -> destination=:1.174 serial=11 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="api.dbusserver">
    <property name="count" type="i" access="readwrite"/>
    <signal name="countChanged">
      <arg name="newCount" type="i" direction="out"/>
    </signal>
    <method name="setCount">
      <arg name="newCount" type="i" direction="in"/>
    </method>
    <method name="reset">
    </method>
  </interface>
  <interface name="org.freedesktop.DBus.Properties">
    <method name="Get">
      <arg name="interface_name" type="s" direction="in"/>
      <arg name="property_name" type="s" direction="in"/>
      <arg name="value" type="v" direction="out"/>
    </method>
    <method name="Set">
      <arg name="interface_name" type="s" direction="in"/>
      <arg name="property_name" type="s" direction="in"/>
      <arg name="value" type="v" direction="in"/>
    </method>
    <method name="GetAll">
      <arg name="interface_name" type="s" direction="in"/>
      <arg name="values" type="a{sv}" direction="out"/>
      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
    </method>
    <signal name="PropertiesChanged">
      <arg name="interface_name" type="s" direction="out"/>
      <arg name="changed_properties" type="a{sv}" direction="out"/>
      <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
      <arg name="invalidated_properties" type="as" direction="out"/>
    </signal>
  </interface>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="xml_data" type="s" direction="out"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus.Peer">
    <method name="Ping"/>
    <method name="GetMachineId">
      <arg name="machine_uuid" type="s" direction="out"/>
    </method>
  </interface>
</node>
"
```

長いのが返ってきました。
登録した Interface である ”api.dbusserver” も見えていますね。

* プロパティ count
  * integer型で読み書き可能
* countChanged シグナル
  * integer型の戻り値1つ
* setCount メソッド
  * integer型の引数1つ
* reset メソッド
  * 引数なし

”org.freedesktop.DBus.Properties” なるインターフェースは後述します。

## サーバーへのアクセス

アクセスに必要な情報は揃ったので叩いてみましょう。

### メソッドの呼び出し

#### reset() の呼び出し

引数なしのメソッドです。
```bash
## reset()
$ dbus-send --session --print-reply --type=method_call --dest=local.myhost /QtDBusDemo/DBusServer api.dbusserver.reset
method return time=1662345793.539809 sender=:1.217 -> destination=:1.219 serial=10 reply_serial=2
```

簡単ですね。
reset() を呼び出したので dbusserver のログに ”reset” とそこから呼ばれる ”setCount” が出ているはずです。

#### setCount() の呼び出し

引数ありのメソッドです。アクセスの仕方は同じ。
引数は定義したとおり int32 型です。

```bash
## setCount()
$ dbus-send --session --print-reply --type=method_call --dest=local.myhost /QtDBusDemo/DBusServer api.dbusserver.setCount int32:10
method return time=1662345887.700270 sender=:1.217 -> destination=:1.223 serial=13 reply_serial=2
```

dbusserver の画面上の表示も変わっていると思います。
また setCount() を呼び出したので dbusserver のログに ”setCount” が出ているはずです。

#### count() の呼び出し

count() にもアクセスしてみましょう。
コード上では公開してないし、D-Bus からとった情報にもないので失敗します。

```bash
## count() will FAIL
$ dbus-send --session --print-reply --type=method_call --dest=local.myhost /QtDBusDemo/DBusServer api.dbusserver.count
Error org.freedesktop.DBus.Error.UnknownMethod: No such method 'count' in interface 'api.dbusserver' at object path '/QtDBusDemo/DBusServer' (signature '')
```

とはいえ、これでは count() の値がとれません。
対応は2つ。

* count()に (reset()と同じように) Q_INVOKABLEをつけて公開しておく
* D-Bus property として Get する

Q_INVOKABLEをつけてみると、アクセスできます。
```git
diff --git a/dbusserver/dbusserver.h b/dbusserver/dbusserver.h
index a7a11d4..76c2c6d 100644
--- a/dbusserver/dbusserver.h
+++ b/dbusserver/dbusserver.h
@@ -15,7 +15,7 @@ public:
     explicit DBusServer(QObject *parent = nullptr);
     ~DBusServer();
 
-    int count() const; // NOT export
+    Q_INVOKABLE int count() const; // export
     Q_INVOKABLE void reset(); // export
 
 public Q_SLOTS:
```
```bash
## Q_INVOKABLE count()
$ dbus-send --session --print-reply --type=method_call --dest=local.myhost /QtDBusDemo/DBusServer api.dbusserver.count
method return time=1662346098.213333 sender=:1.231 -> destination=:1.234 serial=12 reply_serial=2
   int32 10
```

次はプロパティとしてアクセスしてみましょう。

### プロパティの操作

「プロパティ」はD-Busの共通した操作としてアクセスできます。
```
  <interface name="org.freedesktop.DBus.Properties">
    <method name="Get"></method>
    <method name="Set"></method>
    <method name="GetAll"></method>
    <signal name="PropertiesChanged"></signal>
  </interface>
```

Get は、interface と プロパティ名を渡します。

```bash
## service.interface の count を読み出し
$ dbus-send --session --print-reply --type=method_call --dest=local.myhost /QtDBusDemo/DBusServer org.freedesktop.DBus.Properties.Get string:api.dbusserver string:count
method return time=1662346028.965687 sender=:1.217 -> destination=:1.229 serial=18 reply_serial=2
   variant       int32 10
```
dbusserver のログに ”count” が出ているので count() にアクセスしていることがわかります。

Set は、interface とプロパティ名、さらに variant 型で int32 型の値を渡します。
```bash
## service.interface の count に 21 を書き込み
$ dbus-send --session --print-reply --type=method_call --dest=local.myhost /QtDBusDemo/DBusServer org.freedesktop.DBus.Properties.Set string:api.dbusserver string:count variant:int32:21
method return time=1662346130.582755 sender=:1.231 -> destination=:1.235 serial=14 reply_serial=2
```
dbusserver のログに ”setCount” が出ているので setCcount() にアクセスしていることがわかります。


## モニタリング

次は通信自体を監視してみましょう。

### バス全体のモニタリング
dbusserver と dbusclient を実行して動作状態にしておきます。
以下のコマンドを実行して、
```bash
## セッションバス全体をモニタリング
$ dbus-monitor --session
```
dbusclient を操作するとログが出てきます。
しかし、セッションバス全体を見ているので dbusclient 以外の、例えば pulseaudio や WindowManager のアクセスも全部ログに出てきてしまいます。

### 監視対象を絞る
dbusserver の Path のアクセスだけに限定してみましょう。引数に指定するだけです。

```bash
## /QtDBusDemo/DBusServer のものだけをモニタリング
$ dbus-monitor --session path=/QtDBusDemo/DBusServer
signal time=1662346199.077912 sender=org.freedesktop.DBus -> destination=:1.237 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
   string ":1.237"
signal time=1662346199.077940 sender=org.freedesktop.DBus -> destination=:1.237 serial=4 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameLost
   string ":1.237"

## dbusclient で 13 を 'send'
method call time=1662346219.949451 sender=:1.236 -> destination=local.myhost serial=16 path=/QtDBusDemo/DBusServer; interface=api.dbusserver; member=setCount
   int32 13
signal time=1662346219.950398 sender=:1.231 -> destination=(null destination) serial=17 path=/QtDBusDemo/DBusServer; interface=api.dbusserver; member=countChanged
   int32 13
method call time=1662346219.951031 sender=:1.236 -> destination=local.myhost serial=17 path=/QtDBusDemo/DBusServer; interface=org.freedesktop.DBus.Properties; member=Get
   string "api.dbusserver"
   string "count"

## dbuclient で 'reset'
method call time=1662346268.393464 sender=:1.236 -> destination=local.myhost serial=18 path=/QtDBusDemo/DBusServer; interface=api.dbusserver; member=reset
signal time=1662346268.394331 sender=:1.231 -> destination=(null destination) serial=20 path=/QtDBusDemo/DBusServer; interface=api.dbusserver; member=countChanged
   int32 0
method call time=1662346268.395057 sender=:1.236 -> destination=local.myhost serial=19 path=/QtDBusDemo/DBusServer; interface=org.freedesktop.DBus.Properties; member=Get
   string "api.dbusserver"
   string "count"
```
D-Bus 上の接続名が以下のようになっていて、senderの項目で誰のアクセスかがわかります。

* :1.231 dbusserver
* :1.236 dbusclient
* :1.237 dbus-monitor

例えば setCount() のときの動きを見てみると、以下のようになっています。

1. dbusclient から 'setCount' にアクセス
2. (dbusserver 内部の動き)
   1. dbusserver の setCount() が実行される
   2. 値が変更されると dbusserver が SIGNAL(countChanged) を emit
3. SIGNAL が D-Bus 上に通知される
4. それを受けた dbusclient が dbusserver のプロパティ 'count' を Get
   1. (これは QtQuick のプロパティバインディングによる)

## まとめ

DBus サーバーアプリとクライアントアプリを作成して、CLI からそれを叩けるようになりました。
また、通信を監視してログがとれました。

デバッグなどに少しでも役立てば幸いです。