D-Bus のメソッド呼び出しの引数として独自の型を使用する。
公開したいクラスを書く。このクラスに独自型(custom type)を使用したシグナル、スロット、プロパティを加えていく。
以下の例で使用する「公開したいクラス」は次のものである。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #ifndef CHAT_HPP #define CHAT_HPP #include <QObject> #include <QStringList> class Message; class Chat : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "demo.Chat") Q_PROPERTY( QStringList users READ users) signals: void userAdded(const QString& user); void userRemoved(const QString& user); void messageSent(const Message &message); public slots: void addUser(const QString &user); void removeUser(const QString &user); void sendMessage(const Message &message); public: Chat(QObject* parent = nullptr); virtual ~Chat(); QStringList users() const; private: QStringList m_users; }; #endif // CHAT_HPP |
このクラスは、単純なユーザ管理機能を持ち、メッセージ送信手続きを備えている。この例では、Message クラスが独自の型である。Message クラスには、「ユーザ」と「テキスト・メッセージ」だけが入っている。Chat クラスのメソッドで QString の引数を2つ取るようにすることができないわけではない。けれども、そのようにすると Qt が全てを自動で処理できるようになってしまうため、このチュートリアルでは変則的なやり方を採用する必要があるのである。
Chat クラスでは、Qt が標準で多くの型に対応していることを示すため、QStringList を使用している。この点については、後ほど詳しく説明する。
Q_CLASSINFO
宣言は、xml ツールが使用する「インタフェイス名」を指定するためのものである。
通常はインタフェイスの名前の中に会社名を含めるので、宣言は次のようなものになる。
Q_CLASSINFO("D-Bus Interface", "com.firm.department.product.interface")
("D-Bus Interface", "com.会社名.部署名.製品名.インタフェイス名")
Qt に付属しているツール qdbuscpp2xml を使用して自作のクラスの XML 定義ファイル(xml description)を生成する。
上の例のファイル Chat.hpp
に対して qdbuscpp2xml
を実行して、次の出力を得る。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <!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="demo.Chat"> <property name="users" type="as" access="read"/> <signal name="userAdded"> <arg name="user" type="s" direction="out"/> </signal> <signal name="userRemoved"> <arg name="user" type="s" direction="out"/> </signal> <method name="addUser"> <arg name="user" type="s" direction="in"/> </method> <method name="removeUser"> <arg name="user" type="s" direction="in"/> </method> </interface> </node> |
この XML ファイルには Chat クラスのメソッドの中、Qt 標準の型を使用しているもの全てが含まれていることに注目してほしい。もしこの XML ファイルに基いてアダプタ・クラスやインタフェイス・クラスを生成するのであれば、「ユーザ」の追加と削除、「ユーザ」の一覧の取得、及びシグナル「userAdded」(ユーザが追加された)と「userRemoved」(ユーザが削除された)の受信が可能になるであろう。QStringList
型も自動で処理されている。(訳註:アダプタ・クラスは D-Bus 経由でオブジェクトを公開するアプリケーションが同オブジェクトを包むために使用するものであり、インタフェイス・クラスは D-Bus で公開されているオブジェクトを利用しようとするアプリケーションが同オブジェクトとやり取りするために使用するものである。)
しかしながら、Message 型を扱うメソッドは利用できない。Message 型を扱えるアダプタとインタフェイス(クラス)を生成するには、上記の XML ファイルを修正する必要がある。
豆知識:自動生成された XML だけを使用してアダプタとインタフェイスを生成することもできる。そうしたアダプタやインタフェイスを後ほど生成するものと比べてみるのは有益である。
ツール qdbusxml2cpp
に独自型(custom type)について教えてやる必要があるので、XML ファイルに型の情報を書き加えなければならない。
これは、annotation (註釈)というタグを指定することで為される。
<annotation name="org.qtproject.QtDBus.QtTypeName" value="*customType*"/>
このタグの構文は、シグナル/メソッドの中で使用するのか、それともプロパティの中で使用するのかによって少し異なるものになるが(プロパティの場合、.In0
は省略できる)、どちらにせよそこそこ簡単で単純である。以下に、QRect 型を扱う XML の例を挙げる(但し、このチュートリアル本体の例とは関係が無い)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <property name="rectangle" type="(iiii)" access="readwrite"> <annotation name="org.qtproject.QtDBus.QtTypeName" value="QRect"/> </property> <signal name="rectangleChanged"> <arg name="rect" type="(iiii)" direction="out"/> <!-- Qt < 5.6.3 & 5.7.0 に対応するには、代わりに "org.qtproject.QtDBus.QtTypeName.In0" を使用する --> <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QRect"/> </signal> <method name="changeRectangle"> <arg name="message" type="(iiii)" direction="in"/> <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QRect"/> </method> |
「type」に何を指定するかについては、それほど悩まなくてよい。複雑すぎてデフォルト・ハンドラで整形・再整形(marshal/unmarshal)するのが難しい型(type)は、独自型(custom type)を用いて処理されることになる。そのため、type に何を指定するのかは実際には問題にならない。全ての type に "(iiii)"
を指定することもできる。
メソッドのいづれかが複雑な戻り値を返す場合、org.qtproject.QtDBus.QtTypeName.Out0
についても annotation タグを書き加える必要がある。
シグナルの引数に複雑な型が含まれている場合、ここでも org.qtproject.QtDBus.QtTypeName.Out0
について annotation タグを書き加える必要がある。但し、Qt < 5.6.3 & 5.7.0 との後方互換性を保つため、通常のメソッドの引数の場合と同様に org.qtproject.QtDBus.QtTypeName.In0
を使用できるようになっている。qdbusxml2cpp
は、5.6.3 & 5.7.0 より後の版でもこの機能に対応しているが、警告を発することになる。
註記:上記の XML は QRect を使用している。QRect はデフォルトで利用できるため、上記のコードは qdbuscpp2xml によって生成することができる。
このチュートリアルの例の中で Chat インタフェイスとして使用されるものの完全版は次の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <!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="demo.Chat"> <property name="users" type="as" access="read"/> <signal name="userAdded"> <arg name="user" type="s" direction="out"/> </signal> <signal name="userRemoved"> <arg name="user" type="s" direction="out"/> </signal> <signal name="messageSent"> <arg name="message" type="a(ii)" direction="out"/> <!-- Qt < 5.6.3 & 5.7.0 にも対応するには、代わりに "org.qtproject.QtDBus.QtTypeName.In0" を使用する --> <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="Message"/> </signal> <method name="addUser"> <arg name="user" type="s" direction="in"/> </method> <method name="removeUser"> <arg name="user" type="s" direction="in"/> </method> <method name="sendMessage"> <arg name="message" type="a(ii)" direction="in"/> <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="Message"/> </method> </interface> </node> |
qdbusxml2cpp
が必要とする情報全てを XML に入れることができたので、次はアダプタ・クラスとインタフェイス・クラスを生成する。
独自型(custom type)を使用するため、いくつかの文を付け加える必要がある。これによって、アダプタ・クラスとインタフェイス・クラスで全ての型情報を見つけられるようになる。
DBusChat の例(訳註:このチュートリアルで扱っているプログラムのこと)では、アダプタ・クラスとインタフェイス・クラスは次の命令を呼び出して作成されている。
qdbusxml2cpp Chat.xml -i Message.hpp -a ChatAdaptor qdbusxml2cpp Chat.xml -i Message.hpp -p ChatInterface
オブジェクトを包む(wrap)アダプタと、同アダプタと対話するインタフェイスとを用意することができたけれども、まだそれらをコンパイルすることはできない。Qt メタ・オブジェクト・システムで独自型(custom type)を処理できるようにするためには、いくらか付け加えなければならないものがあるからである。
独自型(custom type)の定義が書かれているヘッダ・ファイルに次の指示文を書き加えることで、その型を Qt のメタ型として宣言する。
Q_DECLARE_METATYPE
2つの呼び出し qRegisterMetaType
と qDBusRegisterMetaType
を追加し、それによって Qt フレームワークが独自型を処理できるようにする。
例の Message クラスにおいては、この2つの呼び出しを追加するために静的メソッドを1つ導入している。
void Message::registerMetaType() { qRegisterMetaType<Message>("Message"); qDBusRegisterMetaType<Message>(); }
重要:オブジェクトを公開するアプリケーションとオブジェクトを利用するアプリケーションの両方で独自型を登録する必要がある。なぜかというと、両方のアプリケーションでその独自型を処理できる必要があるからである。また、独自型を必要とするアダプタやインタフェイスのメソッドを呼び出す前にその独自型を登録しておくよう気をつけなければならない。Qt は(まだ)処理できない型が使用された場合にエラー出力を表示するが、それでも読者は上記の点に注意を払っておかなければならない。
独自型(custom type)を使用する DBus 呼び出しを実行する場合、Qt フレームワークはインスタンスを整形(marshal:serialize)したり再整形(unmarshal:unserialize)したりする必要がある。
この作業は QDBusArgument の入出演算子(ストリーム演算子)を用いて行われるので、以下で説明するように、独自型のための入出演算子を実装する必要がある。
例中の Message 型の場合、入出演算子の定義はとても単純である。Message クラスには2つの文字列しか入っていないからである。
QDBusArgument &operator<<(QDBusArgument &argument, const Message& message) { argument.beginStructure(); argument << message.m_user; argument << message.m_text; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, Message &message) { argument.beginStructure(); argument >> message.m_user; argument >> message.m_text; argument.endStructure(); return argument; }
QDBusArgument
には、もっと複雑な型を処理する関数も備わっている。
ここまでの説明で、アダプタ・クラスとインタフェイス・クラスを使い始めるための準備と、独自型が自動で処理されるようにするための準備が整った。
オブジェクトを公開するには、まずそのオブジェクトのためのアダプタをインスタンス化し、次いで使用している DBus に同オブジェクトを登録しなければならない。
DBusChat の例では、次のコードで Chat オブジェクトを公開する。
Chat* pChat = new Chat(&a); ChatAdaptor* pChatAdaptor = new ChatAdaptor(pChat); if (!connection.registerService(CHAT_SERVICE)) { qFatal("Could not register service!"); } if (!connection.registerObject(CHAT_PATH, pChat)) { qFatal("Could not register Chat object!"); }
ChatAdaptor のインスタンスは、Chat オブジェクトへ送られてくる DBus リクエストを処理するものである。あるメソッドが呼び出された場合、ChatAdaptor のインスタンスは Chat オブジェクトの該当するスロットを呼び出す。Chat オブジェクトがシグナルを発する場合、ChatAdaptor が DBus を通して同シグナルを転送する。
遠隔のオブジェクトと対話するには、対応するインタフェイスを実体化し(対応するインタフェイス・クラスをインスタンス化し)、正しいサービス名とオブジェクト経路(object path)を渡すだけでよい。
DBusChat の例では、遠隔の Chat オブジェクトとの接続を確立するには次のようなコードを書く。
demo::Chat chatInterface(CHAT_SERVICE, CHAT_PATH, connection);
(訳註:「demo::Chat」等の定義は、チュートリアルの最後に掲載されている。)
一度インタフェイス・クラスのインスタンスを作成すれば、他の QObject とやり取りするのと同じようなやり方で遠隔のオブジェクトとやり取りできるようになる。
例えば、DBusChat の例の ChatWindow クラスでは、次の呼び出しを行うだけでユーザを追加することができる。
m_chatInterface.addUser(m_userName);
また ChatWindow は、他の QObject に接続する(connect())のと同じやり方で Chat オブジェクトのインタフェイス・クラスに接続することによって、同オブジェクトが発したシグナルに応答することができる。
connect(&m_chatInterface, SIGNAL(userAdded(QString)), SLOT(chat_userAdded(QString))); connect(&m_chatInterface, SIGNAL(userRemoved(QString)), SLOT(chat_userRemoved(QString))); connect(&m_chatInterface, SIGNAL(messageSent(Message)), SLOT(chat_messageSent(Message)));
こうした作業を手作業で行うのは望ましいことではない。けれども、dbus の呼び出しを直接構成し返答を直接処理するようなコードを書くよりも、アダプタとインタフェイスを利用する方がはるかに手軽である。
qdbuscpp2xml と qdbusxml2cpp が自分に渡されたヘッダ・ファイルだけを解析するのではなく、コード・ツリー全体を解析するよう拡張され、それによって独自型に対応するようになってほしいと考える人もいるかもしれない。そうなれば、qdbuscpp2xml と qdbusxml2cpp の2つは、独自型を認識できるようになり、それに応じたメソッド、シグナル、プロパティを XML に追加できるようになるからである。qdbuscpp2xml の機能を拡張するやり方としてはプラグインを用いるものが考えられる。このやり方を実装したのが Cpp2XmlPlugins である。
また、Qt Creator に機能を追加して、上のような手法を自動で行うようにするやり方もある。Qt Creator はもともとプロジェクトで使われている独自型を知っている必要があるので、このやり方は qdbus ツールを修正するやり方よりも現実的(少なくともより簡単)かもしれない。
列挙型を大量に使用し且つそれらを DBus 経由で公開する必要がある場合、全ての列挙型に対して QDBusArgument の入出演算子(stream operators)を使用することになるであろう。初歩的な実装としては、単に列挙型を int 型へ、int 型を列挙型へ変換(cast)するものが考えられる。そのコードは次のようなものになろう。
QDBusArgument &operator<<(QDBusArgument &argument, const EnumerationType& source) { argument.beginStructure(); argument << static_cast<int>(source); argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, EnumerationType &dest) { int a; argument.beginStructure(); argument >> a; argument.endStructure(); dest = static_cast<EnumerationType>(a); return argument; }
この変換コードがほとんど同じコードのまま全ての列挙型に対して通用することに気づいたであろう。変える箇所は「EnumerationType」のところだけである。
ありがたいことに、C++ はテンプレートの扱い方を知っているので、テンプレートを使った実装を1つ書くだけで済むはずである。しかしながら、ここで我々が必要としているのは列挙型だけを扱う実装であり、DBus を通して送信しようと考えている他の独自型を扱う実装ではない。そうでなければ、いづれの独自型においても、独自型から整数型への変換、あるいは整数型から独自型への変換が行われてしまうであろう。全ての型が(とりわけ複雑な型が)そのように変換されてしまうのは、おそらく読者の望むところではなかろう。
ここで今どきの C++ 標準ライブラリが役に立つ。ちょっとした魔法によって、テンプレートの定義の中で条件式が使えるのである。
これを用いると、QDBusArgument
の整形(marshaling)と再整形(unmarshalling)の手続きを、条件式が真の場合だけ使用される形で実装することができる。
実際の整形(marshaling)と再整形(unmarshalling)のコードは次のようなものになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | template<typename T, typename TEnum> class QDBusEnumMarshal; template<typename T> class QDBusEnumMarshal<T, std::true_type> { public: static QDBusArgument& marshal(QDBusArgument &argument, const T& source) { argument.beginStructure(); argument << static_cast<int>(source); argument.endStructure(); return argument; } static const QDBusArgument& unmarshal(const QDBusArgument &argument, T &source) { int a; argument.beginStructure(); argument >> a; argument.endStructure(); source = static_cast<T>(a); return argument; } } |
ここまでの作業によって、整形手続きを実装したものを以下のコードで呼び出せるようになった。
QDBusEnumMarshal<T, typename std::is_enum<T>::type>::marshal(argument, source);
T
には整形(marshal)したい型が入る。
この実装を QDbusArgument の入出演算子に結びつけるには、さらに接着剤となるコードが必要である。
template<typename T> QDBusArgument& operator<<(QDBusArgument &argument, const T& source) { return QDBusEnumMarshal<T, typename std::is_enum<T>::type>::marshal(argument, source); } template<typename T> const QDBusArgument& operator>>(const QDBusArgument &argument, T &source) { return QDBusEnumMarshal<T, typename std::is_enum<T>::type>::unmarshal(argument, source); }
上記のもの全てを1つのヘッダ・ファイルに入れ、列挙型(DBus 経由で受け渡す必要があるもの)を宣言したファイルに同ヘッダ・ファイルを取り込む(include)。こうすることで、コンパイラは自力で入出演算子(の定義)を見つけることができるようになる。
但し、依然として列挙型をメタ型として宣言して Qt メタ・オブジェクト・システムに登録する必要はある。
任意の列挙型を整形/再整形(marshal/unmarshal)するのに必要なヘッダ・ファイルの全体は次の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #ifndef _ENUM_DBUS_HPP #define _ENUM_DBUS_HPP #include <QtDBus/QDBusArgument> #include <type_traits> using namespace std; template<typename T, typename TEnum> class QDBusEnumMarshal; template<typename T> class QDBusEnumMarshal<T, std::true_type> { public: static QDBusArgument& marshal(QDBusArgument &argument, const T& source) { argument.beginStructure(); argument << static_cast<int>(source); argument.endStructure(); return argument; } static const QDBusArgument& unmarshal(const QDBusArgument &argument, T &source) { int a; argument.beginStructure(); argument >> a; argument.endStructure(); source = static_cast<T>(a); return argument; } }; template<typename T> QDBusArgument& operator<<(QDBusArgument &argument, const T& source) { return QDBusEnumMarshal<T, typename std::is_enum<T>::type>::marshal(argument, source); } template<typename T> const QDBusArgument& operator>>(const QDBusArgument &argument, T &source) { return QDBusEnumMarshal<T, typename std::is_enum<T>::type>::unmarshal(argument, source); } #endif //_ENUM_DBUS_HPP |
この節に例の chat のソースを載せる。この wiki では画像以外のファイルのアップロードが認められていないので、ソースをテキスト形式で投稿した。
Chat は、公開されるインタフェイス(の名前)である。ChatAdaptor と ChatInterface は Chat.xml を使用して生成されたものである。
これら全てのファイルを1つのディレクトリに入れ、次のコマンドを実行する。
mkdir build pushd build cmake ../ -G Ninja ninja ./dbuschat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #ifndef CHAT_HPP #define CHAT_HPP #include <QObject> #include <QStringList> class Message; class Chat : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "demo.Chat") Q_PROPERTY( QStringList users READ users) signals: void userAdded(const QString& user); void userRemoved(const QString& user); void messageSent(const Message &message); public slots: void addUser(const QString &user); void removeUser(const QString &user); void sendMessage(const Message &message); public: Chat(QObject* parent = nullptr); virtual ~Chat(); QStringList users() const; private: QStringList m_users; }; #endif // CHAT_HPP |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include "Chat.hpp" Chat::Chat(QObject* parent) : QObject(parent) { } Chat::~Chat() { } void Chat::addUser(const QString &user) { if (!m_users.contains(user)) { m_users.append(user); emit userAdded(user); } } void Chat::removeUser(const QString &user) { if (m_users.contains(user)) { m_users.removeOne(user); emit userRemoved(user); } } void Chat::sendMessage(const Message &message) { Q_EMIT messageSent(message); } QStringList Chat::users() const { return m_users; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <!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="demo.Chat"> <property name="users" type="as" access="read"/> <signal name="userAdded"> <arg name="user" type="s" direction="out"/> </signal> <signal name="userRemoved"> <arg name="user" type="s" direction="out"/> </signal> <signal name="messageSent"> <arg name="message" type="a(ii)" direction="out"/> <!-- to support also Qt < 5.6.3 & 5.7.0, use instead "org.qtproject.QtDBus.QtTypeName.In0" --> <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="Message"/> </signal> <method name="addUser"> <arg name="user" type="s" direction="in"/> </method> <method name="removeUser"> <arg name="user" type="s" direction="in"/> </method> <method name="sendMessage"> <arg name="message" type="a(ii)" direction="in"/> <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="Message"/> </method> </interface> </node> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp Chat.xml -i Message.hpp -a ChatAdaptor * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * This file may have been hand-edited. Look for HAND-EDIT comments * before re-generating it. */ #ifndef CHATADAPTOR_H_1270658274 #define CHATADAPTOR_H_1270658274 #include <QtCore/QObject> #include <QtDBus/QtDBus> #include "Message.hpp" class QByteArray; template<class T> class QList; template<class Key, class Value> class QMap; class QString; class QStringList; class QVariant; /* * Adaptor class for interface demo.Chat */ class ChatAdaptor: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "demo.Chat") Q_CLASSINFO("D-Bus Introspection", "" " <interface name=\"demo.Chat\">\n" " <property access=\"read\" type=\"as\" name=\"users\"/>\n" " <signal name=\"userAdded\">\n" " <arg direction=\"out\" type=\"s\" name=\"user\"/>\n" " </signal>\n" " <signal name=\"userRemoved\">\n" " <arg direction=\"out\" type=\"s\" name=\"user\"/>\n" " </signal>\n" " <signal name=\"messageSent\">\n" " <arg direction=\"out\" type=\"a(ii)\" name=\"message\"/>\n" " <!-- to support also Qt < 5.6.3 & 5.7.0, use instead org.qtproject.QtDBus.QtTypeName.In0 -->\n" " <annotation value=\"Message\" name=\"com.trolltech.QtDBus.QtTypeName.Out0\"/>\n" " </signal>\n" " <method name=\"addUser\">\n" " <arg direction=\"in\" type=\"s\" name=\"user\"/>\n" " </method>\n" " <method name=\"removeUser\">\n" " <arg direction=\"in\" type=\"s\" name=\"user\"/>\n" " </method>\n" " <method name=\"sendMessage\">\n" " <arg direction=\"in\" type=\"a(ii)\" name=\"message\"/>\n" " <annotation value=\"Message\" name=\"com.trolltech.QtDBus.QtTypeName.In0\"/>\n" " </method>\n" " </interface>\n" "") public: ChatAdaptor(QObject *parent); virtual ~ChatAdaptor(); public: // PROPERTIES Q_PROPERTY(QStringList users READ users) QStringList users() const; public Q_SLOTS: // METHODS void addUser(const QString &user); void removeUser(const QString &user); void sendMessage(Message message); Q_SIGNALS: // SIGNALS void messageSent(Message message); void userAdded(const QString &user); void userRemoved(const QString &user); }; #endif |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp Chat.xml -i Message.hpp -a ChatAdaptor * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * Do not edit! All changes made to it will be lost. */ #include "ChatAdaptor.h" #include <QtCore/QMetaObject> #include <QtCore/QByteArray> #include <QtCore/QList> #include <QtCore/QMap> #include <QtCore/QString> #include <QtCore/QStringList> #include <QtCore/QVariant> /* * Implementation of adaptor class ChatAdaptor */ ChatAdaptor::ChatAdaptor(QObject *parent) : QDBusAbstractAdaptor(parent) { // constructor setAutoRelaySignals(true); } ChatAdaptor::~ChatAdaptor() { // destructor } QStringList ChatAdaptor::users() const { // get the value of property users return qvariant_cast< QStringList >(parent()->property("users")); } void ChatAdaptor::addUser(const QString &user) { // handle method call demo.Chat.addUser QMetaObject::invokeMethod(parent(), "addUser", Q_ARG(QString, user)); } void ChatAdaptor::removeUser(const QString &user) { // handle method call demo.Chat.removeUser QMetaObject::invokeMethod(parent(), "removeUser", Q_ARG(QString, user)); } void ChatAdaptor::sendMessage(Message message) { // handle method call demo.Chat.sendMessage QMetaObject::invokeMethod(parent(), "sendMessage", Q_ARG(Message, message)); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp Chat.xml -i Message.hpp -p ChatInterface * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * Do not edit! All changes made to it will be lost. */ #ifndef CHATINTERFACE_H_1270658265 #define CHATINTERFACE_H_1270658265 #include <QtCore/QObject> #include <QtCore/QByteArray> #include <QtCore/QList> #include <QtCore/QMap> #include <QtCore/QString> #include <QtCore/QStringList> #include <QtCore/QVariant> #include <QtDBus/QtDBus> #include "Message.hpp" /* * Proxy class for interface demo.Chat */ class DemoChatInterface: public QDBusAbstractInterface { Q_OBJECT public: static inline const char *staticInterfaceName() { return "demo.Chat"; } public: DemoChatInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0); ~DemoChatInterface(); Q_PROPERTY(QStringList users READ users) inline QStringList users() const { return qvariant_cast< QStringList >(property("users")); } public Q_SLOTS: // METHODS inline QDBusPendingReply<> addUser(const QString &user) { QList<QVariant> argumentList; argumentList << qVariantFromValue(user); return asyncCallWithArgumentList(QLatin1String("addUser"), argumentList); } inline QDBusPendingReply<> removeUser(const QString &user) { QList<QVariant> argumentList; argumentList << qVariantFromValue(user); return asyncCallWithArgumentList(QLatin1String("removeUser"), argumentList); } inline QDBusPendingReply<> sendMessage(Message message) { QList<QVariant> argumentList; argumentList << qVariantFromValue(message); return asyncCallWithArgumentList(QLatin1String("sendMessage"), argumentList); } Q_SIGNALS: // SIGNALS void messageSent(Message message); void userAdded(const QString &user); void userRemoved(const QString &user); }; namespace demo { typedef ::DemoChatInterface Chat; } #endif |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp Chat.xml -i Message.hpp -p ChatInterface * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * This file may have been hand-edited. Look for HAND-EDIT comments * before re-generating it. */ #include "ChatInterface.h" /* * Implementation of interface class DemoChatInterface */ DemoChatInterface::DemoChatInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) { } DemoChatInterface::~DemoChatInterface() { } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | #ifndef CHATWINDOW_HPP #define CHATWINDOW_HPP #include <QMainWindow> #include <QStringListModel> #include "ChatInterface.h" namespace Ui { class ChatWindow; } class QCloseEvent; class ChatWindow : public QMainWindow { Q_OBJECT public: ChatWindow(demo::Chat& chatInterface, QWidget *parent = nullptr); virtual ~ChatWindow(); protected: virtual void closeEvent(QCloseEvent *event); private: ChatWindow(const ChatWindow &other); ChatWindow& operator=(const ChatWindow &other); Ui::ChatWindow *ui; QString m_userName; QStringListModel m_users; demo::Chat &m_chatInterface; private slots: void sendMessage(); void chat_userAdded(const QString &user); void chat_userRemoved(const QString &user); void chat_messageSent(const Message &message); }; #endif // CHATWINDOW_HPP |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | #include "ChatWindow.hpp" #include "ui_ChatWindow.h" #include <QtGui/QCloseEvent> #include <QInputDialog> #include <QMessageBox> #include "Message.hpp" ChatWindow::ChatWindow(demo::Chat& chatInterface, QWidget *parent) : QMainWindow(parent), ui(new Ui::ChatWindow), m_userName(), m_users(), m_chatInterface(chatInterface) { ui->setupUi(this); QStringList userList = chatInterface.users(); m_users.setStringList(userList); m_users.sort(0); connect(&m_chatInterface, SIGNAL(userAdded(QString)), SLOT(chat_userAdded(QString))); connect(&m_chatInterface, SIGNAL(userRemoved(QString)), SLOT(chat_userRemoved(QString))); connect(&m_chatInterface, SIGNAL(messageSent(Message)), SLOT(chat_messageSent(Message))); connect(ui->lneMessage, SIGNAL(returnPressed()), SLOT(sendMessage())); connect(ui->btnSend, SIGNAL(released()), SLOT(sendMessage())); bool ok; m_userName = QInputDialog::getText(this, "Username?", "User name:", QLineEdit::Normal, QDir::home().dirName(), &ok, Qt::Dialog); if (!ok) { QApplication::exit(); qFatal("Cancelled"); } if (userList.contains(m_userName)) { QMessageBox::critical(this, "Invalid Username", "Username " + m_userName + " is already taken"); QApplication::exit(); qFatal("Duplicate username"); } m_chatInterface.addUser(m_userName); setWindowTitle("Chat -- " + m_userName); ui->lstUsers->setModel(&m_users); } ChatWindow::~ChatWindow() { delete ui; } void ChatWindow::closeEvent(QCloseEvent *event) { QMainWindow::closeEvent(event); if (event->isAccepted()) { m_chatInterface.removeUser(m_userName); } } void ChatWindow::sendMessage() { Message message(m_userName, ui->lneMessage->text()); m_chatInterface.sendMessage(message); ui->lneMessage->clear(); } void ChatWindow::chat_userAdded(const QString &user) { QStringList users = m_users.stringList(); users.append(user); users.sort(); m_users.setStringList(users); } void ChatWindow::chat_userRemoved(const QString &user) { QStringList users = m_users.stringList(); users.removeOne(user); m_users.setStringList(users); } void ChatWindow::chat_messageSent(const Message &message) { ui->txtChat->appendPlainText(message.getUser() + " : " + message.getText()); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ChatWindow</class> <widget class="QMainWindow" name="ChatWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>600</width> <height>400</height> </rect> </property> <property name="windowTitle"> <string>ChatWindow</string> </property> <widget class="QWidget" name="centralWidget"> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QWidget" name="widget_2" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QListView" name="lstUsers"> <property name="maximumSize"> <size> <width>150</width> <height>16777215</height> </size> </property> <property name="focusPolicy"> <enum>Qt::NoFocus</enum> </property> <property name="editTriggers"> <set>QAbstractItemView::NoEditTriggers</set> </property> <property name="selectionMode"> <enum>QAbstractItemView::NoSelection</enum> </property> </widget> </item> <item> <widget class="QPlainTextEdit" name="txtChat"> <property name="focusPolicy"> <enum>Qt::NoFocus</enum> </property> </widget> </item> </layout> </widget> </item> <item> <widget class="QWidget" name="widget" native="true"> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QLineEdit" name="lneMessage"/> </item> <item> <widget class="QPushButton" name="btnSend"> <property name="text"> <string>Send</string> </property> </widget> </item> </layout> </widget> </item> </layout> </widget> <widget class="QMenuBar" name="menuBar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>600</width> <height>25</height> </rect> </property> </widget> <widget class="QToolBar" name="mainToolBar"> <attribute name="toolBarArea"> <enum>TopToolBarArea</enum> </attribute> <attribute name="toolBarBreak"> <bool>false</bool> </attribute> </widget> <widget class="QStatusBar" name="statusBar"/> </widget> <layoutdefault spacing="6" margin="11"/> <resources/> <connections/> </ui> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #ifndef MESSAGE_HPP #define MESSAGE_HPP #include <QtDBus> class Message { public: Message(); Message(const QString& user, const QString &message); Message(const Message &other); Message& operator=(const Message &other); ~Message(); friend QDBusArgument &operator<<(QDBusArgument &argument, const Message &message); friend const QDBusArgument &operator>>(const QDBusArgument &argument, Message &message); QString getUser() const; QString getText() const; //register Message with the Qt type system static void registerMetaType(); private: QString m_user; QString m_text; }; Q_DECLARE_METATYPE(Message) #endif // MESSAGE_HPP |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #include "Message.hpp" Message::Message() : m_user(), m_text() { } Message::Message(const QString &user, const QString &text) : m_user(user), m_text(text) { } Message::Message(const Message &other) : m_user(other.m_user), m_text(other.m_text) { } Message& Message::operator=(const Message &other) { m_user = other.m_user; m_text = other.m_text; return *this; } Message::~Message() { } QString Message::getUser() const { return m_user; } QString Message::getText() const { return m_text; } void Message::registerMetaType() { qRegisterMetaType<Message>("Message"); qDBusRegisterMetaType<Message>(); } QDBusArgument &operator<<(QDBusArgument &argument, const Message& message) { argument.beginStructure(); argument << message.m_user; argument << message.m_text; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, Message &message) { argument.beginStructure(); argument >> message.m_user; argument >> message.m_text; argument.endStructure(); return argument; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | #include <QtDBus/QDBusConnection> #include <QtDBus/QDBusConnectionInterface> #include <QApplication> #include "Chat.hpp" #include "ChatAdaptor.h" #include "ChatInterface.h" #include "ChatWindow.hpp" #include "Message.hpp" #define CHAT_SERVICE "demo.dbus.chat" #define CHAT_PATH "/chat" int main(int argc, char *argv[]) { /* Register the Message type first thing, so Qt knows how to handle it before an Adaptor/Interface is even constructed. It should be ok to register it further down, as long as no Message marshaling or unmarshaling takes place, but this is definitely the safest way of doing things. */ Message::registerMetaType(); QApplication a(argc, argv); Chat* pChat = NULL; ChatAdaptor* pChatAdaptor = NULL; /* Create a Chat instance and register it with the session bus only if the service isn't already available. */ QDBusConnection connection = QDBusConnection::sessionBus(); if (!connection.interface()->isServiceRegistered(CHAT_SERVICE)) { pChat = new Chat(&a); pChatAdaptor = new ChatAdaptor(pChat); if (!connection.registerService(CHAT_SERVICE)) { qFatal("Could not register service!"); } if (!connection.registerObject(CHAT_PATH, pChat)) { qFatal("Could not register Chat object!"); } } demo::Chat chatInterface(CHAT_SERVICE, CHAT_PATH, connection); ChatWindow w(chatInterface); w.show(); int ret = a.exec(); //cleanup if (pChat) { delete pChat; } if (pChatAdaptor) { delete pChatAdaptor; } return ret; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | project(dbuschat) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) cmake_minimum_required(VERSION 3.0) set(QT_MIN_VERSION "5.6.0") ################# Disallow in-source build ################# if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") message(FATAL_ERROR "This application requires an out of source build. Please create a separate build directory.") endif() include(FeatureSummary) ################# Find dependencies ################# find_package(Qt5 COMPONENTS Widgets DBus REQUIRED) ################# build and install ################# set(dbuschat_SRCS ChatWindow.ui ChatWindow.cpp Chat.cpp Message.cpp Message.hpp ChatAdaptor.cpp ChatInterface.cpp ) add_executable(dbuschat main.cpp ${dbuschat_SRCS}) target_link_libraries(dbuschat Qt5::Widgets Qt5::DBus ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) |
前の記事
D-Bus のインタフェイスを作成する