Wayland: 出力画面

出力画面
今回は、出力画面の情報 (モニタの解像度など) を取得します。

$ cc -o test 07-output.c -lwayland-client

<07-output.c>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <wayland-client.h>


//------------

typedef struct
{
    struct wl_list link;
    struct wl_output *output;
    uint32_t id,ver;
}output_item;

struct wl_list g_list;

struct wl_display *g_display;
int g_display_callback_cnt = 0;

//------------


//----------------------
// output リストデータ
//----------------------


/* 全アイテムを削除 */

static void _list_destroy(void)
{
    output_item *pi,*tmp;

    wl_list_for_each_safe(pi, tmp, &g_list, link)
    {
        if(pi->ver >= WL_OUTPUT_RELEASE_SINCE_VERSION)
            wl_output_release(pi->output);
        else
            wl_output_destroy(pi->output);

        free(pi);
    }
}

/* アイテム追加 */

static void _list_append(uint32_t id,uint32_t ver,struct wl_output *output)
{
    output_item *pi;

    pi = (output_item *)calloc(1, sizeof(output_item));
    if(!pi) return;

    pi->output = output;
    pi->id = id;
    pi->ver = ver;

    wl_list_insert(g_list.prev, &pi->link);
}

/* 指定 ID を削除 */

static void _list_delete(uint32_t id)
{
    output_item *pi;

    wl_list_for_each(pi, &g_list, link)
    {
        if(pi->id == id)
        {
            if(pi->ver >= WL_OUTPUT_RELEASE_SINCE_VERSION)
                wl_output_release(pi->output);
            else
                wl_output_destroy(pi->output);
            
            wl_list_remove(&pi->link);
            free(pi);

            break;
        }
    }
}


//----------------------
// wl_output
//----------------------


static void _output_geometry(void *data, struct wl_output *wl_output,
    int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel,
    const char *make, const char *model, int32_t transform)
{
    printf("<geometry>\n"
        "  x:%d, y:%d, physical_width:%d mm, physical_height:%d mm\n"
        "  subpixel:%d, make:'%s', model:'%s', transform:%d\n",
        x, y, physical_width, physical_height,
        subpixel, make, model, transform);
}

static void _output_mode(void *data, struct wl_output *wl_output, uint32_t flags,
    int32_t width, int32_t height, int32_t refresh)
{
    printf("mode | flags:0x%X, width:%d, heigh:%d, refresh:%d mHz\n",
        flags, width, height, refresh);
}

static void _output_done(void *data, struct wl_output *wl_output)
{
    printf("done\n");
}

static void _output_scale(void *data, struct wl_output *wl_output, int32_t factor)
{
    printf("scale | factor:%d\n", factor);
}

static void _output_name(void *data, struct wl_output *wl_output, const char *name)
{
    printf("name | '%s'\n", name);
}

static void _output_description(void *data, struct wl_output *wl_output, const char *description)
{
    printf("description | '%s'\n", description);
}

static const struct wl_output_listener g_output_listener =
{
    _output_geometry, _output_mode, _output_done, _output_scale,
    _output_name, _output_description
};


//-------------------------
// wl_callback (display)
//-------------------------


static void _display_callback_done(void *data,struct wl_callback *callback,uint32_t time)
{
    wl_callback_destroy(callback);

    g_display_callback_cnt--;

    printf("-- display callback done\n");
}

static const struct wl_callback_listener g_display_callback_listener = {
    _display_callback_done
};


//-------------------------
// sub
//-------------------------


/* イベントの同期コールバック追加 */

static void _add_display_sync(void)
{
    struct wl_callback *callback;

    callback = wl_display_sync(g_display);

    wl_callback_add_listener(callback, &g_display_callback_listener, NULL);

    g_display_callback_cnt++;
}

/* wl_output のバインド */

static void _add_output(struct wl_registry *reg,uint32_t id,uint32_t ver)
{
    struct wl_output *output;

    output = wl_registry_bind(reg, id, &wl_output_interface, ver);

    _list_append(id, ver, output);

    wl_output_add_listener(output, &g_output_listener, NULL);

    //同期

    _add_display_sync();
}


//---------------------
// wl_registry
//---------------------


static void _registry_global(
    void *data,struct wl_registry *reg,uint32_t id,const char *name,uint32_t ver)
{
    if(strcmp(name, "wl_output") == 0)
        _add_output(reg, id, (ver > 4)? 4: ver);
}

static void _registry_global_remove(void *data,struct wl_registry *registry,uint32_t id)
{
    _list_delete(id);
}

static const struct wl_registry_listener g_reg_listener = {
    _registry_global, _registry_global_remove
};


//----------------


int main(void)
{
    struct wl_registry *reg;

    g_display = wl_display_connect(NULL);
    if(!g_display) return 1;

    //wl_list 初期化

    wl_list_init(&g_list);

    //wl_registry

    reg = wl_display_get_registry(g_display);

    wl_registry_add_listener(reg, &g_reg_listener, NULL);

    _add_display_sync();

    //すべてのイベントが終わるまで待つ

    while(wl_display_dispatch(g_display) != -1
        && g_display_callback_cnt != 0);

    //

    _list_destroy();

    wl_registry_destroy(reg);

    wl_display_disconnect(g_display);

    return 0;
}
実行結果
-- display callback done
<geometry>
  x:0, y:0, physical_width:480 mm, physical_height:270 mm
  subpixel:0, make:'PHL', model:'PHL 224E5', transform:0
mode | flags:0x3, width:1920, heigh:1080, refresh:60000 mHz
scale | factor:1
name | 'HDMI-1'
description | 'Philips Consumer Electronics Company 22"'
done
-- display callback done
wl_output
wl_output は、接続されているモニタの情報を取得するインターフェイスです。

2025年1月時点で、最大バージョンは「4」なので、ここでは ver 4 で使用する場合の情報を説明します。
バインド
wl_registry の global イベントで、wl_output をバインドすることになりますが、他のグローバルオブジェクトと違って、複数のモニタが接続されている場合は、異なる ID で複数の wl_output が存在するので、注意してください。

今回は、複数の wl_output を扱えるようにするため、wl_list を使用します。
リストのデータとして、「wl_output *, ID, バージョン数値」を格納します。
破棄
wl_output は、オブジェクトを破棄するための方法として、2種類の関数があります。

void wl_output_destroy(struct wl_output *wl_output);
void wl_output_release(struct wl_output *wl_output); //ver 3

wl_output_release() は、wl_output の ver 3 以降で使用できます。
マクロで判定したい場合、WL_OUTPUT_RELEASE_SINCE_VERSION が 3 の値になっているので、バインド時のバージョンがこの値以上であれば、使用できます。

destroy はオブジェクトを直接破棄しますが、release は、クライアントがこのオブジェクトを今後使用しないことを、サーバーに通知します。

release を使った場合、実際にオブジェクトを破棄するタイミングはサーバーに委ねられるので、状況によっては、サーバー内部でしばらくオブジェクトが残る場合があります。

特定のインターフェイスで、destroy と release の2つが存在する場合は、実際に使用されているバージョンを判定した上で、release を優先して使うといいでしょう。
global_remove イベント
wl_registry の global_remove イベントで、wl_output の ID が渡された場合、そのモニタの接続が外されるなどして、その出力が使えなくなったことを意味します。

その場合は、リストデータ内から渡された ID を検索して、その wl_output を解放します。
イベントの同期
今回は、wl_output が複数回バインドされる可能性があるので、すべての wl_output のイベントを待ちたい場合は、注意が必要です。

wl_registry_add_listener() の後に wl_display_roundtrip() を1回行うだけでは、wl_output のイベントまで処理できません。
(wl_registry_add_listener の直後に生成されるイベントは、wl_registry の global イベントだけなので、global イベント内で wl_output をバインドすることによって生成された、wl_output のイベントは対象外になります)

任意の時点で生成されたイベントがすべて処理されるまで待ちたい場合は、wl_display の同期コールバックを使います。

static void _display_callback_done(void *data,struct wl_callback *callback,uint32_t time)
{
    wl_callback_destroy(callback);

    g_display_callback_cnt--;

    printf("-- display callback done\n");
}

static const struct wl_callback_listener g_display_callback_listener = {
    _display_callback_done
};

/* イベントの同期コールバック追加 */

static void _add_display_sync(void)
{
    struct wl_callback *callback;

    callback = wl_display_sync(g_display);

    wl_callback_add_listener(callback, &g_display_callback_listener, NULL);

    g_display_callback_cnt++;
}
wl_display_sync()
struct wl_callback *wl_display_sync(struct wl_display *wl_display);

wl_display_sync() を使うと、"現時点で生成されているすべてのイベント" の処理が終了した時に、コールバック関数を呼ぶことができます。

wl_display_sync() の戻り値で wl_callback のポインタが返るので、それを元に、他のインターフェイスにハンドラを設定する時と同じように、コールバックの関数をセットします。
wl_callback
wl_callback は、何かしらの処理が終わった時にイベントを受け取るだけのインターフェイスです。

int wl_callback_add_listener(struct wl_callback *wl_callback,
    const struct wl_callback_listener *listener, void *data);

struct wl_callback_listener {
    void (*done)(void *data, struct wl_callback *wl_callback, uint32_t callback_data);
};

//破棄

void wl_callback_destroy(struct wl_callback *wl_callback);

関数の戻り値で取得した wl_callback に関連する処理が終わった時、done イベントが発生します。
コールバックの処理
wl_callback の done イベント内では、wl_callback_destroy() を使って wl_callback を破棄します。

今回の場合、done イベントが呼ばれたら、イベントがすべて終了したということなので、done イベントが呼ばれたかどうかだけを判断すればいいことになります。

なお、wl_display_sync() は、実行した時点で存在するイベントのみが対象となるため、その後に新しく生成されたイベントは対象外になります。

そのため、同期したい地点ごとにコールバックを設定し、カウンタ値を加算します。
done イベントが呼ばれたらカウンタ値を減算し、カウンタ値が 0 になったら、イベントはすべて終了したということになります。

これを元に、現在セットされているすべてのコールバックが終了した時 (すべての同期対象イベントが終わった時) まで待ちたい場合は、以下のようにします。

while(wl_display_dispatch(g_display) != -1
        && g_display_callback_cnt != 0);

wl_display_dispatch() でイベントの処理&イベント待ちを行い、一度戻ってきたら、同期イベントが終了しているかを判断します。
同期の追加タイミング
今回の場合は、wl_registry の取得と、各 wl_output のバインド後に、同期を追加しています。

wl_registry が取得された時は、各 global イベントが生成されているので、それを待ちます。
wl_output のバインド時は、その出力の情報を取得するためのイベントが生成されているので、それを待ちます。

それらすべてのイベントの処理が終了した時点で、プログラムを終了しています。
同期のキャンセル
もしも、途中で wl_callback による同期をキャンセルしたい場合は、まだ done が呼ばれていない wl_callback を、wl_callback_destroy() で破棄する必要があります。

そのため、コールバックをキャンセルする可能性がある場合は、wl_callback のポインタを保持しておく必要があります。
wl_output イベント
モニタに関する情報が、イベントで送られてきます。

struct wl_output_listener {
    void (*geometry)(void *data, struct wl_output *wl_output,
        int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel,
        const char *make, const char *model, int32_t transform);
    void (*mode)(void *data, struct wl_output *wl_output, uint32_t flags,
        int32_t width, int32_t height, int32_t refresh);
    //ver 2
    void (*done)(void *data, struct wl_output *wl_output);
    void (*scale)(void *data, struct wl_output *wl_output, int32_t factor);
    //ver 4
    void (*name)(void *data, struct wl_output *wl_output, const char *name);
    void (*description)(void *data, struct wl_output *wl_output, const char *description);
};
geometry イベント
void (*geometry)(void *data, struct wl_output *wl_output,
  int32_t x, int32_t y, int32_t physical_width,
  int32_t physical_height, int32_t subpixel,
  const char *make, const char *model, int32_t transform);

モニタの基本情報が送られてきます。
バインドされた時と、各プロパティのいずれかが変更された時に呼ばれます。

x, y左上の px 位置
physical_width
physical_height
物理的な幅と高さ (mm)。

モニタの表示部分のサイズとなっています。
VirtualBox 上では、0 となりました。
subpixelモニタのサブピクセルの並び。

WL_OUTPUT_SUBPIXEL_UNKNOWN
WL_OUTPUT_SUBPIXEL_NONE
WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB
WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR
WL_OUTPUT_SUBPIXEL_VERTICAL_RGB
WL_OUTPUT_SUBPIXEL_VERTICAL_BGR
makeモニタのメーカー名
modelモニタのモデル名
transform回転や反転の状態。

WL_OUTPUT_TRANSFORM_NORMAL
WL_OUTPUT_TRANSFORM_90
WL_OUTPUT_TRANSFORM_180
WL_OUTPUT_TRANSFORM_270
WL_OUTPUT_TRANSFORM_FLIPPED
WL_OUTPUT_TRANSFORM_FLIPPED_90
WL_OUTPUT_TRANSFORM_FLIPPED_180
WL_OUTPUT_TRANSFORM_FLIPPED_270
mode イベント
void (*mode)(void *data, struct wl_output *wl_output, uint32_t flags,
  int32_t width, int32_t height, int32_t refresh);

モニタが設定可能なモードの情報が送られてきます。

1回は必ず呼ばれ、複数のモードが使用可能な場合は、複数回呼ばれます。
また、現在のモニタのモードが変更された時も、再度送信されます。

flagsフラグ。
WL_OUTPUT_MODE_CURRENT (1) : 現在使われているモード
WL_OUTPUT_MODE_PREFERRED (2) : 優先されるモード
width
height
幅と高さ (px)。画面全体のサイズ。
refreshリフレッシュレート (mHz)。
Hz 単位にする場合は、1000 で割る。
done イベント
//ver 2
void (*done)(void *data, struct wl_output *wl_output);

mode イベントなど、各イベントをグループとして扱う時に、ひとかたまりの情報が終わった後に送られてきます。
複数の情報をすべて取得した後に、何かしらの処理を行いたい場合は、このイベントを受け取った時に行います。
scale イベント
//ver 2
void (*scale)(void *data, struct wl_output *wl_output, int32_t factor);

画面のスケーリング (拡大) 情報を受け取ります。

バインド直後、またはスケールが変更された時に送信されます。
もし送られてこなかった場合は、値を 1 として仮定する必要があります。

factor の値が 1 より大きい場合は、コンポーザが、レンダリング時にウィンドウなどのイメージを拡大させて出力します。
主にモバイル端末などで、モニタが高解像度なことにより、そのままでは画面が見にくい時に使われます。

クライアントがスケーリングに対応する場合は、wl_surface_set_buffer_scale() にこの factor の値を渡して、倍率を適用します。
name イベント
//ver 4
void (*name)(void *data, struct wl_output *wl_output, const char *name);

この出力の名前 (UTF-8)。
明確な規則はなく、複数の wl_output が存在する場合は、それぞれ異なる名前になります。
HDMI で接続されていれば、"HDMI-1" というような名前になります。

バインド時に1度だけ送信されます。
description イベント
//ver 4
void (*description)(void *data, struct wl_output *wl_output, const char *description);

この出力に関する詳細説明の文字列 (UTF-8)。

バインド直後、または説明の内容が変更されるたびに送信されます。
オプションの情報のため、全く送信されない場合もあります。