このページでは

カスタムシェル

「カスタムシェル」では、カスタムシェル拡張機能を実装する方法について説明しています。

Wayland向けのシェル拡張機能は、ウィンドウの状態、位置、サイズを管理するプロトコルです。ほとんどのコンポジターは、組み込みの拡張機能のうち1つ以上をサポートしていますが、状況によっては、アプリケーションに必要な機能を正確に備えたカスタム拡張機能を作成できると便利な場合があります。

赤、緑、白の壁がある3Dルームと「ライトマップを使用する」チェックボックスが表示された、Qt Quick 3Dのベイク済みライトマップのサンプルを実行しているCompositor

これには、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 を使用する場合、WAYLANDSERVERSOURCES およびWAYLANDCLIENTSOURCES 変数でも同様の結果が得られます。)

クライアントプラグイン

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は安定していると見なされており、ソース互換性は維持されます。この点では、Qtの他のプラグインAPIと同様です。

ExampleShellIntegration は、前述のようにシェルサーフェスを作成するためのクライアント側のエントリポイントです。これは、Curiously Recurring Template Pattern(CRTP)を用いて、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 が初期化されると、アプリケーションはサーバーに接続され、コンポジターがサポートするグローバルインターフェースのブロードキャストを受信します。成功した場合、そのインターフェースに対するリクエストを発行できます。 このケースでは、サポートすべきリクエストは「シェルサーフェスの作成」のみです。組み込み関数 `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

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

QtWayland::qt_example_shell_surface クラスには、イベントが1つしかありません。

    void example_shell_surface_minimize(uint32_t minimized) override;

ExampleShellSurface は、内部のウィンドウ状態を更新するためにこれを再実装しています。ウィンドウ状態が変更されると、保留中の状態を後で処理するために一時的に保存し、QWaylandShellSurface 内のapplyConfigureWhenPossible() を呼び出します。状態、サイズ、位置の変更は、このように整理する必要があります。そうすることで、変更がサーフェスへのレンダリングに干渉しないことを保証し、複数の関連する変更を1つの操作として容易に適用できるようになります。

サーフェスの再構成が安全に行えるようになった時点で、仮想関数applyConfigure() が呼び出されます。

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

ここで、新しい(最小化または最大化)状態を実際にウィンドウに反映させます。

2つ目のスーパークラスは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。

「Custom Shell」コンポジターにおける注目すべき違いの一つは、シェル拡張機能のインスタンス化です。「Minimal QML」の例では、シェル拡張機能IviApplication 、XdgShell 、およびWlShell をインスタンス化していますが、「Custom Shell」の例では、ExampleShell 拡張機能のインスタンスのみを作成しています。

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

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

ExampleShell は、プロトコルXMLで定義されたAPIを含む、生成されたQtWaylandServer::qt_example_shell インターフェースのサブクラスです。また、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()
{
    QWaylandCompositorExtensionTemplate::initialize();

    QWaylandCompositor*compositor = static_cast<QWaylandCompositor*>(extensionContainer());
    if(!compositor) {
        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);
}

この仮想関数は、クライアントが接続に対してこのリクエストを発行するたびに呼び出されます。

このシェル拡張機能は 1 つの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

これで設定は完了です。「Custom Shell」のサンプルは、機能がごく限られたシェル拡張機能ですが、特化した拡張機能を構築するための出発点として活用できます。

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

© 2026 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.