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,ver;
}output_item;

struct wl_list g_list;

struct wl_display *g_display;
int g_display_callback_cnt = 0;

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


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


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

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 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(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 追加

    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 > 3)? 3: 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;
}
wl_output
モニタの情報を取得するには、wl_output が必要です。

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

wl_registry の global イベントでバインドするのですが、他のグローバルオブジェクトと違い、複数のモニタが接続されている場合は、複数の wl_output が存在するため、複数の wl_output をバインドする必要があります。
複数の wl_output
複数のモニタが接続されている場合、複数の wl_output が、異なる識別子で存在します。

複数の wl_output に対応するために、各識別子と、バインドした wl_output ポインタを記録しておく必要があります。

今回は、複数の wl_output を格納するリストデータとして、wayland-util.h で定義されている wl_list を使いますが、別のリスト構造を使っても構いません。
wl_registry : global イベント処理
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)。

モニタの表示部分のサイズとなっています。
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 の値を渡して、倍率を適用します。
イベントの同期
今回の場合、wl_output が複数回バインド&ハンドラ設定される可能性があるので、wl_registry のハンドラ内で生成されたイベントが、すべて終わるまで待ちたい場合、これまでのように wl_display_roundtrip() を複数回実行するのではなく、wl_display の同期コールバックを使うのが適切です。

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

戻り値に wl_callback のポインタが返るので、それを使ってコールバックのハンドラをセットします。

wl_display_sync() を呼んだ時点で生成されている、すべてのイベントのハンドラ関数が、クライアントで処理されて終了した時点で、指定したコールバック関数が呼ばれます。

なお、wl_display_sync() の後に生成されたイベントに関しては対象外となるため、同期させたいイベントが生成されるたびに、この同期処理を実行する必要があります。

コールバックの処理
今回は、同期のコールバックが追加される度にカウンタを加算し、コールバック関数が呼ばれたら、カウンタを減算するという形で実装しています。

カウンタが 0 になった場合は、待っているイベントが一つもないということになります。

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

wl_display_dispatch() は、キューにあるイベントを処理する関数です。
イベントキューが空の場合は、イベントが発生するまで待ちます。

同期のコールバックがすべて呼ばれて、カウンタが 0 になったら、終了しています。

同期の追加タイミング
今回の場合は、wl_registrywl_output の作成後に、同期を追加しています。

この2つにおいて、オブジェクトの作成と同時に生成されるイベントが、クライアント内ですべて処理されたら、プログラムを終了しています。

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

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