カスタム拡張機能

Custom ExtensionはカスタムWaylandエクステンションの実装方法を示します。

Wayland用の新しい拡張機能を書くのは簡単です。これらはXMLベースのフォーマットで定義され、wayland-scanner ツールがこれをCのグルーコードに変換します。Qtはこれをqtwaylandscanner で拡張し、QtとC++で追加のグルーコードを生成します。

Custom Extensionの例では、これらのツールを使ってWaylandプロトコルを拡張し、Waylandクライアントとサーバー間でカスタムリクエストやイベントを送信する方法を示します。

この例は4つの項目から構成されている:

  • プロトコルの定義
  • 拡張機能をサポートするコンポジター
  • 拡張機能をサポートするC++ベースのクライアント
  • 拡張機能をサポートするQMLベースのクライアント

プロトコルの定義

XML ファイルcustom.xml はプロトコルを定義しています。このファイルには "qt_example_extension" というインターフェースが含まれています。これはサーバからブロードキャストされ、クライアントがリクエストを送信したりイベントを受信したりするためにアタッチする名前です。この名前は一意であるべきなので、公式のインターフェースとは異なるプレフィックスを使用するのが良いでしょう。

インターフェースは通常、リクエストと イベントという2種類のリモートプロシージャ コールから構成される。「リクエスト "はクライアントがサーバー側で行う呼び出しで、"イベント "はサーバーがクライアント側で行う呼び出しです。

エクステンションの例では、クライアントウィンドウに特定のトランスフォームを適用するようサーバーに指示するリクエストのセットを含んでいます。例えば、クライアントが "bounce "リクエストを送信した場合、サーバーはウィンドウを画面上で跳ねさせることでこれに応答する必要があります。

同様に、サーバーがクライアントに指示を与えるために使用できる一連のイベントがある。例えば、"set_font_size "イベントは、クライアントがデフォルトのフォントサイズを特定のサイズに設定するための命令である。

プロトコルはリクエストとイベントの存在と、それらが取る引数を定義する。qtwaylandscanner が実行されると、プロシージャコールとその引数をマーシャリングし、これをコネクション上で送信するために必要なコードが生成されます。相手側では、これが仮想関数への呼び出しとなり、実際の応答を提供するために実装することができる。

qtwaylandscanner をビルドの一部として自動的に実行させるために、CMake 関数のqt_generate_wayland_protocol_server_sources()qt_generate_wayland_protocol_client_sources()を使用して、それぞれサーバー側とクライアント側のグルー・コードを生成します。(qmake を使用する場合は、WAYLANDSERVERSOURCESWAYLANDCLIENTSOURCES 変数で同じことができます)。

コンポジターの実装

Compositorアプリケーション自体はQMLとQt Quickを使って実装されていますが、拡張機能はC++で実装されています。

最初のステップは、qtwaylandscanner によって生成されたグルー・コードのサブクラスを作成し、その機能にアクセスできるようにすることです。QMLからアクセスできるように、QML_ELEMENT マクロをクラスに追加します。

class CustomExtension  : public QWaylandCompositorExtensionTemplate<CustomExtension>
        , public QtWaylandServer::qt_example_extension
{
    Q_OBJECT
    QML_ELEMENT

生成されたクラスを継承するだけでなく、Curiously Recurring Templateパターンを使って拡張機能を扱う際の利便性を提供するクラスQWaylandCompositorExtensionTemplate も継承します。

QWaylandCompositorExtensionTemplateQObject ベースのクラスなので、継承リストで最初になければならないことに注意してください。

サブクラスは生成された基底クラスの仮想関数の再実装を持っており、クライアントによって発行されたリクエストを処理できます。

protected:
    void example_extension_bounce(Resource *resource, wl_resource *surface, uint32_t duration) override;

これらの再実装では、コンポジターの実際のQMLコードで扱えるように、単にリクエストをシグナル発信に変換します。

void CustomExtension::example_extension_bounce(QtWaylandServer::qt_example_extension::Resource *resource, wl_resource *wl_surface, uint32_t duration)
{
    Q_UNUSED(resource);
    auto surface = QWaylandSurface::fromResource(wl_surface);
    qDebug() << "server received bounce" << surface << duration;
    emit bounce(surface, duration);
}

さらに、このサブクラスではイベントごとにスロットを定義し、QMLから呼び出したり、シグナルに接続できるようにしています。スロットは単にイベントをクライアントに送信する生成関数を呼び出すだけです。

void CustomExtension::setFontSize(QWaylandSurface *surface, uint pixelSize)
{
    if (surface) {
        Resource *target = resourceMap().value(surface->waylandClient());
        if (target) {
            qDebug() << "Server-side extension sending setFontSize:" << pixelSize;
            send_set_font_size(target->handle,  surface->resource(), pixelSize);
        }
    }
}

クラス定義にQML_ELEMENT マクロを追加したので(対応するビルドステップをビルドシステムファイルに追加しました)、QMLでインスタンス化することができます。

コンポジターがこのオブジェクトを拡張機能として登録できるように、WaylandCompositor オブジェクトの直接の子オブジェクトにしています。

    CustomExtension {
        id: custom

        onSurfaceAdded: (surface) => {
            var item = itemForSurface(surface)
            item.isCustom = true
        }

        onBounce: (surface, ms) => {
            var item = itemForSurface(surface)
            item.doBounce(ms)
        }

        onSpin: (surface, ms) => {
            var item = itemForSurface(surface)
            item.doSpin(ms)
        }

        onCustomObjectCreated: (obj) => {
            var item = customObjectComponent.createObject(defaultOutput.surfaceArea,
                                                          { "color": obj.color,
                                                            "text": obj.text,
                                                            "obj": obj } )
        }
    }

    function setDecorations(shown) {
        var n = itemList.length
        for (var i = 0; i < n; i++) {
            if (itemList[i].isCustom)
                custom.showDecorations(itemList[i].surface.client, shown)
        }
    }

このオブジェクトはクライアントからのリクエストに対応するシグナルハンドラを持ち、それに応じて反応します。さらに、スロットを呼び出してイベントを送信することもできます。

            onFontSizeChanged: {
                custom.setFontSize(surface, fontSize)
            }

C++クライアントの実装

どちらのクライアントも、インターフェースのC++実装を共有している。コンポジターと同様に、生成されたコードもテンプレート・クラスを継承したサブクラスを作ります。この場合、QWaylandClientExtensionTemplateを継承します。

class CustomExtension : public QWaylandClientExtensionTemplate<CustomExtension>
        , public QtWayland::qt_example_extension

アプローチはコンポジターと非常に似ていますが、逆になっています:リクエストは生成された関数を呼び出すスロットとして実装され、イベントはシグナルを発するために再実装する仮想関数として実装されます。

void CustomExtension::sendBounce(QWindow *window, uint ms)
{
    QtWayland::qt_example_extension::bounce(getWlSurface(window), ms);
}

クライアントのコード自体は非常にシンプルで、動作をトリガーする方法を示すことだけを目的としている。カスタムペイントイベントでは、矩形とラベルのセットを描画する。これらのいずれかがクリックされると、サーバーにリクエストを発行します。

    void mousePressEvent(QMouseEvent *ev) override
    {
        if (rect1.contains(ev->position()))
            doSpin();
        else if (rect2.contains(ev->position()))
            doBounce();
        else if (rect3.contains(ev->position()))
            newWindow();
        else if (rect4.contains(ev->position()))
            newObject();
    }

set_font_size イベントを受信したときにフォントサイズを更新するために、拡張クラスのシグナルはスロットに接続されています。

        connect(m_extension, &CustomExtension::fontSize, this, &TestWindow::handleSetFontSize);

スロットはフォントサイズを更新し、ウィンドウを再描画します。

QMLクライアントの実装

QMLクライアントはC++クライアントと似ています。C++ クライアントと同じカスタム拡張モジュールの実装に依存し、それを QML でインスタンス化して有効にしています。

    CustomExtension {
        id: customExtension
        onActiveChanged: {
            registerWindow(topLevelWindow)
        }
        onFontSize: (window, pixelSize) => {
            topLevelWindow.fontSize = pixelSize
        }
    }

UI はクリック可能な矩形で構成され、矩形がクリックされたときにTapHandler を使って対応するリクエストを送信します。

            TapHandler {
                onTapped: {
                    if (customExtension.active)
                        customExtension.sendBounce(topLevelWindow, 1000)
                }
            }

簡単のため、この例ではbouncespin のリクエストとset_font_size のイベントについてのみ説明します。追加機能のサポートを追加することは、読者のための練習として残されている。

サンプルプロジェクト @ code.qt.io

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。