Wayland - 出力画面

出力画面
出力画面の情報を取得します。

$ cc -o test 10_output.c -lwayland-client

<10_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;
}output_item;

struct wl_list g_list;

struct wl_display *g_display;
int g_display_callback_cnt = 0;

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


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

/* 破棄 */

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

    wl_list_for_each_safe(pi, tmp, &g_list, link)
    {
        wl_output_destroy(pi->output);
        free(pi);
    }
}

/* 追加 */

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

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

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

    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)
        {
            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 const struct wl_output_listener g_output_listener =
{
    _output_geometry, _output_mode,
    _output_done, _output_scale,
};

//------------- 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()
{
    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 version)
{
    struct wl_output *output;

    //output 追加

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

    _list_append(id, 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 *itf,uint32_t ver)
{
    if(strcmp(itf, "wl_output") == 0)
        _add_output(reg, id, (ver > 2)? 2: 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_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;
}

出力結果
#======= GNOME

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

#======= weston (X11 上)

geometry:
  x 0, y 0, physical_width 270 mm, physical_height 158 mm
  subpixel 0, make 'weston-X11', model 'none', transform 0
scale: factor 1
mode: flags 0x3, width 1024, heigh 600, refresh 60000 mHz

#======= weston-launch

geometry:
  x 0, y 0, physical_width 480 mm, physical_height 270 mm
  subpixel 0, make 'PHL', model 'PHL 224E5', transform 0
scale: factor 1
mode: flags 0x3, width 1920, heigh 1080, refresh 60000 mHz
mode: flags 0x0, width 1920, heigh 1080, refresh 59940 mHz
mode: flags 0x0, width 1920, heigh 1080, refresh 60000 mHz
mode: flags 0x0, width 1920, heigh 1080, refresh 59940 mHz
mode: flags 0x0, width 1920, heigh 1080, refresh 50000 mHz
mode: flags 0x0, width 1920, heigh 1080, refresh 50000 mHz
mode: flags 0x0, width 1680, heigh 1050, refresh 59883 mHz
mode: flags 0x0, width 1280, heigh 1024, refresh 75025 mHz
mode: flags 0x0, width 1280, heigh 1024, refresh 60020 mHz
mode: flags 0x0, width 1440, heigh 900, refresh 74984 mHz
mode: flags 0x0, width 1440, heigh 900, refresh 59901 mHz
mode: flags 0x0, width 1280, heigh 720, refresh 60000 mHz
mode: flags 0x0, width 1280, heigh 720, refresh 59940 mHz
...
説明
wl_output
Wayland でモニタの情報を取得するためには、wl_output が必要です。

2017/10 時点での最新バージョンは 3 です。
今回は、使用する最大バージョンを 2 とします。

なお、ver 3 で wl_output_release() が追加されているので、
ver 3 以降を使う場合は破棄時に wl_output_release() を使い、
それ未満の場合は、wl_output_destroy() を使います。

複数の wl_output
複数のモニタがある場合、複数の wl_output が異なる識別子で存在するので、複数の wl_output に対応するために、リストデータを使って、その識別子とバインドした wl_output ポインタを記録しておく必要があります。

今回は、wayland-util.h で定義されている wl_list を使いますが、別のリスト構造を使っても構いません。

wl_registry ハンドラ内処理
wl_registry の global ハンドラ内で、"wl_output" が来たら、バインド&ハンドラ設定をして、識別子と wl_output ポインタをリストデータに追加します。

global_remove イベントが来た時は、そのモニタが使えなくなったことを意味するので、リストデータ内からその識別子を検索して、wl_output を破棄します。
wl_output イベントハンドラ
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)。

実際の Wayland 環境で実行した場合は、モニタの表示部分のサイズとなっています。
weston (X11 上) では、画面サイズに DPI を適用した値となっていました。
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
void (*done)(void *data, struct wl_output *wl_output);

mode イベントなど、すべての情報の送信が終わった後に送られてきます。
情報をすべて取得した後に何かしらの処理を行いたい場合は、このイベントを受け取った時に行います。

* ver 2 以降

scale
void (*scale)(void *data, struct wl_output *wl_output, int32_t factor);

画面のスケーリング (拡大) 情報を受け取ります。
バインド直後、またはスケールが変更された時に送信されます。
もし送られてこなかった場合は、値を 1 として仮定する必要があります。

factor の値が 1 より大きい場合、コンポーザがレンダリング時にサーフェスバッファを拡大させます。
モニタが高解像度で、画像が見にくい場合に使われます。

クライアントがスケーリングに対応する場合は、wl_surface_set_buffer_scale() に factor の値を渡して、倍率を適用します。

* ver 2 以降
イベントの同期
今回は、wl_output が複数回バインド&ハンドラ設定される可能性があるので、wl_registry のハンドラ内で生成されたイベントがすべて終わるまで待ちたい場合、これまでのように wl_display_roundtrip() を使うのは適切ではありません。

今回は、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()
{
    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()
wl_display_sync() を使うと、現時点で生成されているすべてのイベントが終了した時に、コールバックを呼ぶことができます。
作成された wl_callback が戻り値で返るので、それを使ってコールバックハンドラをセットします。

すべてのイベントのハンドラ関数がクライアントで処理されて終了した時点で、指定したコールバック関数が呼ばれます。

なお、wl_display_sync() は、この関数が呼ばれた時点で生成されているイベントの処理を待つので、この関数の後に追加されたイベントに関しては対象外となります。

コールバックの処理
今回は、コールバックが追加される度にカウンタを加算し、コールバック関数が呼ばれたらカウンタを減算する形で実装しています。
カウンタが 0 の場合は、待っているイベントがないということになります。

ただし、途中でコールバックをキャンセルしたい場合、まだ呼ばれていないコールバックを wl_callback_destroy() で破棄しなければならないので、コールバックをキャンセルする可能性がある場合は、wl_callback のポインタを保存しておく必要があります。

コールバックの追加タイミング
今回の場合は、wl_registrywl_output の作成後にコールバックを追加しています。
この2つで、オブジェクトの作成と同時に生成されるイベントがクライアント内ですべて処理されたら、プログラムを終了しています。

つまり、サーバーの接続時に最初から使えるインターフェイスがすべて列挙され、すべての wl_output で初期情報の送信が終わった時に、すべての同期コールバックが終了することになります。