カスタムシェル

カスタムシェルは、カスタムシェルエクステンションを実装する方法を示します。

Waylandのシェル拡張はウィンドウの状態、位置、サイズを管理するプロトコルです。ほとんどのコンポジターは1つ以上のビルトイン拡張機能をサポートしていますが、状況によっては、アプリケーションが必要とする機能を正確に含むカスタム拡張機能を書くことができると便利です。

この場合、Wayland接続のサーバーサイドとクライアントサイドの両方でシェル拡張機能を実装する必要があります。

カスタムシェルの例ではシンプルなシェル拡張の実装を示しています。これは3つの部分に分かれています:

  • カスタムシェルインターフェースのプロトコル記述。
  • クライアントアプリケーションでインターフェイスに接続するためのプラグイン。
  • インターフェイスのサーバー側実装を持つコンポジターの例。

プロトコル記述はwayland-scanner で読み込まれる標準的な XML フォーマットに従っています。ここでは詳しく説明しませんが、以下の機能をカバーしています:

  • wl_surface のシェルサーフェスを作成するためのインターフェイス。 これにより、プロトコルは既存のwl_surface API の上に機能を追加することができる。
  • シェルサーフェスにウィンドウタイトルを設定するリクエスト。
  • シェルサーフェスを最小化/非最小化するリクエスト。
  • シェルサーフェスの現在の最小化状態をクライアントに通知するイベント。

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

クライアント・プラグイン

シェル統合をQtクライアントから検出するためには、QWaylandShellIntegrationPluginを再実装する必要があります。

class QWaylandExampleShellIntegrationPlugin : public QWaylandShellIntegrationPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QWaylandShellIntegrationFactoryInterface_iid FILE "example-shell.json")

public:
    QWaylandShellIntegration *create(const QString &key, const QStringList &paramList) override;
};

QWaylandShellIntegration *QWaylandExampleShellIntegrationPlugin::create(const QString &key, const QStringList &paramList)
{
    Q_UNUSED(key);
    Q_UNUSED(paramList);
    return new ExampleShellIntegration();
}

これはシェル統合に "example-shell "キーをアタッチし、クライアントがインターフェースに接続したときにExampleShellIntegration クラスがインスタンス化される方法を提供します。

シェル拡張を作成するためのAPIはヘッダーqwaylandclientshellapi_p.h

#include <QtWaylandClient/private/qwaylandclientshellapi_p.h>

このヘッダにはプライベートAPIを含める必要があります。なぜなら、パブリックなQt APIとは異なり、バイナリ互換性が保証されていないからです。このAPIは安定したものであり、ソースとの互換性も保たれています。

ExampleShellIntegration は、上記のようにシェルサーフェスを作成するためのクライアントサイドのエントリーポイントです。これは、Curiously Recurring Templateパターンを使用して、QWaylandShellIntegrationTemplateクラスを継承しています。

class Q_WAYLANDCLIENT_EXPORT ExampleShellIntegration
        : public QWaylandShellIntegrationTemplate<ExampleShellIntegration>
        , public QtWayland::qt_example_shell
{
public:
    ExampleShellIntegration();

    QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override;
};

また、プロトコルの XML 記述に基づいてqtwaylandscanner が生成するQtWayland::qt_example_shell クラスも継承しています。

コンストラクタは、サポートするプロトコルのバージョンを指定します:

ExampleShellIntegration::ExampleShellIntegration()
    : QWaylandShellIntegrationTemplate(/* Supported protocol version */ 1)
{
}

example_shell プロトコルは現在バージョン1なので、親クラスに1 を渡します。これはプロトコルのネゴシエーションで使用され、コンポジターが新しいバージョンのプロトコルを使用しても古いクライアントが動作し続けるようにします。

ExampleShellIntegration が初期化されると、アプリケーションはサーバーに接続され、コンポジターがサポートするグローバル・インターフェースのブロードキャストを受け取ります。成功すれば、インターフェイスに対するリクエストを発行できる。この場合、サポートするリクエストは1つだけです:シェルサーフェスの作成です。組み込み関数wlSurfaceForWindow() を使って QWaylandWindow をwl_surface に変換し、リクエストを発行します。そして、返されたサーフェスをExampleShellSurface オブジェクトで拡張し、qt_example_shell_surface インターフェースのリクエストとイベントを処理します。

QWaylandShellSurface *ExampleShellIntegration::createShellSurface(QWaylandWindow *window)
{
    if (!isActive())
        return nullptr;
    auto *surface = surface_create(wlSurfaceForWindow(window));
    return new ExampleShellSurface(surface, window);
}

ExampleShellSurface は2つのクラスを継承しています。

class ExampleShellSurface : public QWaylandShellSurface
        , public QtWayland::qt_example_shell_surface

ひとつはプロトコルのXML記述に基づいて生成されるQtWayland::qt_example_shell_surface クラスである。これはイベント用の仮想関数と、プロトコルのリクエスト用の通常のメンバ関数を提供する。

QtWayland::qt_example_shell_surface クラスは単一のイベントしか持たない。

    void example_shell_surface_minimize(uint32_t minimized) override;

ExampleShellSurface はこれを再インプリメントして、内部のウィンドウの状態を更新する。ウィンドウの状態が変更されると、保留中の状態を後まで保存し、QWaylandShellSurfaceapplyConfigureWhenPossible() を呼び出す。状態、サイズ、位置の変更はこのように整理する必要がある。そうすることで、変更がサーフェスへのレンダリングに干渉しないようにし、関連する複数の変更を1つの変更として簡単に適用できるようにする。

サーフェスの再構成が安全になると、仮想applyConfigure() 関数が呼び出される。

void ExampleShellSurface::applyConfigure()
{
    if (m_stateChanged)
        QWindowSystemInterface::handleWindowStateChanged(platformWindow()->window(), m_pendingStates);
    m_stateChanged = false;
}

ここで実際に新しい(最小化または最小化解除された)状態をウィンドウにコミットする。

つ目のスーパークラスはQWaylandShellSurface 。これはWaylandのQPAプラグインとQWaylandWindowがシェルとの通信に使用するインターフェースです。ExampleShellSurface はこのインターフェイスからいくつかの仮想関数も再実装しています。

    bool wantsDecorations() const override;
    void setTitle(const QString &) override;
    void requestWindowStates(Qt::WindowStates states) override;
    void applyConfigure() override;

例えば、Qtアプリケーションがウィンドウのタイトルを設定するとき、これは仮想関数setTitle() の呼び出しに変換されます。

void ExampleShellSurface::setTitle(const QString &windowTitle)
{
    set_window_title(windowTitle);
}

ExampleShellSurface 、これはカスタムのシェル・サーフェス・インターフェースへのリクエストに変換されます。

コンポジター

この例の最後の部分はコンポジターそのものです。これは他のコンポジターの例と同じ一般的な構造を持っています。コンポジターの構成要素の詳細については、Minimal QMLの例を参照してください。 Qt Wayland Compositor.

カスタムシェルコンポジターで顕著な違いは、シェル拡張のインスタンス化です。Minimal QMLの例ではシェル拡張IviApplicationXdgShellWlShell のインスタンスを作成しますが、Custom Shellの例ではExampleShell のインスタンスのみを作成します。

ExampleShell {
    id: shell
    onShellSurfaceCreated: (shellSurface) => {
        shellSurfaces.append({shellSurface: shellSurface});
    }
}

グローバルインターフェースとして登録するために、WaylandCompositor の直接の子としてシェル拡張モジュールのインスタンスを作成します。これは、クライアントが接続するときにブロードキャストされ、前のセクションで説明したように、クライアントはインターフェイスにアタッチできるようになります。

ExampleShell は生成されたQtWaylandServer::qt_example_shell インターフェイスのサブクラスであり、プロトコルの XML で定義された API を含んでいる。また、QWaylandCompositorExtensionTemplate のサブクラスでもある。これは、QWaylandCompositor によってオブジェクトが拡張として認識されることを保証する。

class ExampleShell
        : public QWaylandCompositorExtensionTemplate<ExampleShell>
        , QtWaylandServer::qt_example_shell

この二重継承は、Qt Wayland Compositor で拡張機能を構築する際の典型的なパターンである。QWaylandCompositorExtensionTemplate クラスは、QWaylandCompositorExtension と、qtwaylandscanner によって生成されたqt_example_shell クラスとの間の接続を作成します。

同様に、ExampleShellSurface クラスは生成されたQtWaylandServer::qt_example_shell_surface クラスとQWaylandShellSurfaceTemplate を継承し、ShellSurface クラスのサブクラスとなり、Qt Wayland Compositor と生成されたプロトコル・コードの間の接続を確立します。

この型をQt Quick で利用できるようにするために、Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS プリプロセッサ・マクロを使用します。特に、Qt Quick グラフに追加されたときに、拡張機能を自動的に初期化する処理を行います。

voidExampleShell::initialize() { ::initialize(); ::initialize(); ::initialize()    QWaylandCompositorExtensionTemplate::initialize();    QWaylandCompositor*compositor =  static_cast<QWaylandCompositor*>(extensionContainer());if(!compositor) {*compositor= static_cast<        qWarning() << "Failed to find QWaylandCompositor when initializing ExampleShell";
       return; } init(compositor->display(), 1); }

initialize() 関数のデフォルトの実装では、拡張機能をコンポジターに登録します。これに加えて、プロトコル拡張自体を初期化します。これは、QtWaylandServer::qt_example_shell_surface クラスで生成されたinit() 関数を呼び出すことで行います。

また、surface_create リクエスト用に生成された仮想関数も再実装します。

void ExampleShell::example_shell_surface_create(Resource *resource, wl_resource *surfaceResource, uint32_t id)
{
    QWaylandSurface *surface = QWaylandSurface::fromResource(surfaceResource);

    if (!surface->setRole(ExampleShellSurface::role(), resource->handle, QT_EXAMPLE_SHELL_ERROR_ROLE))
        return;

    QWaylandResource shellSurfaceResource(wl_resource_create(resource->client(), &::qt_example_shell_surface_interface,
                                                           wl_resource_get_version(resource->handle), id));

    auto *shellSurface = new ExampleShellSurface(this, surface, shellSurfaceResource);
    emit shellSurfaceCreated(shellSurface);
}

この仮想関数は、クライアントがコネクション上で リクエストを発行するたびに呼び出されます。

私たちのシェル拡張は単一のQWaylandSurfaceRole をサポートするだけであるが、そのためのシェルサーフェスを作成するときにQWaylandSurface に割り当てることは重要である。この主な理由は、同じサーフェスに競合するロールを割り当てることはプロトコルエラーとみなされ、このエラーが発生した場合、このエラーを発行するのはコンポジターの責任であるためです。サーフェスを採用するときにロールを設定することで、サーフェスが後で別のロールで再利用されたときにプロトコルエラーが発行されるようになります。

組み込み関数を使用してWaylandとQtの型を変換し、ExampleShellSurface オブジェクトを作成します。すべての準備が整ったら、shellSurfaceCreated() シグナルを発信します。このシグナルはQMLコードでインターセプトされ、シェルサーフェスのリストに追加されます。

ExampleShell {
    id: shell
    onShellSurfaceCreated: (shellSurface) => {
        shellSurfaces.append({shellSurface: shellSurface});
    }
}

ExampleShellSurface では、プロトコル拡張のシェルサーフェス部分を有効にします。

例の実行

クライアントが新しいシェル拡張にうまく接続できるようにするには、いくつかの設定を行う必要があります。

まず、クライアントはシェル拡張のプラグインを見つけなければなりません。これを行う簡単な方法の一つは、QT_PLUGIN_PATH をプラグインのインストールディレクトリに設定することです。Qt はカテゴリごとにプラグインを検索するので、プラグインのパスはwayland-shell-integration というカテゴリのディレクトリを含む親ディレクトリを指すようにします。つまり、インストールされたファイルが/path/to/build/plugins/wayland-shell-integration/libexampleshellplugin.so であれば、QT_PLUGIN_PATH を次のように設定します:

export QT_PLUGIN_PATH=/path/to/build/plugins

プラグインディレクトリを設定する他の方法については、プラグインのドキュメントを参照してください。

最後のステップは、クライアントが実際に正しいシェル拡張にアタッチすることを確認することです。Qt クライアントは自動的に組み込みのシェル拡張にアタッチしようとしますが、QT_WAYLAND_SHELL_INTEGRATION 環境変数にロードする拡張の名前を設定することで、これをオーバーライドすることができます。

export QT_WAYLAND_SHELL_INTEGRATION=example-shell

これで全てです。カスタムシェルの例は、ごくわずかな機能しか持たない限定的なシェル拡張ですが、特殊な拡張を構築するための出発点として使用することができます。

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

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.