Wayland - ウィンドウ用イメージを作る

ウィンドウ用イメージを作る
実際に、共有メモリでウィンドウ表示用のイメージを作成してみます。
イメージを作成するだけなので、まだウィンドウは表示できません。

shm_open() を使うのに librt が必要なので、リンクを追加します。

$ cc -o test 04_shmimg.c -lwayland-client -lrt

<04_shmimg.c>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

#include <wayland-client.h>

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

struct wl_shm *g_shm;
unsigned int g_shm_cnt = 0;

typedef struct
{
    struct wl_shm_pool *pool;
    struct wl_buffer *buffer;
    void *data;
    int size;
}imagebuf;

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

/** POSIX 共有メモリオブジェクトを作成 */

static int _create_posix_shm()
{
    char name[64];
    int ret;

    while(1)
    {
        snprintf(name, 64, "/wayland-test-%d", g_shm_cnt);

        ret = shm_open(name, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0600);

        if(ret >= 0)
        {
            shm_unlink(name);

            g_shm_cnt++;
            break;
        }
        else if(errno == EEXIST)
            //同名が存在する
            g_shm_cnt++;
        else if(errno != EINTR)
        {
            printf("failed shm_open()\n");
            break;
        }
    }

    return ret;
}

/** wl_shm_pool 作成 */

static struct wl_shm_pool *_create_shm_pool(int size,void **ppbuf)
{
    int fd;
    void *data;
    struct wl_shm_pool *pool;

    fd = _create_posix_shm();
    if(fd < 0) return NULL;

    printf("posix shm: %d\n", fd);

    //サイズ変更

    if(ftruncate(fd, size) < 0)
    {
        close(fd);
        return NULL;
    }

    //メモリにマッピング

    data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if(data == MAP_FAILED)
    {
        close(fd);
        return NULL;
    }

    printf("mmap: %p\n", data);

    //wl_shm_pool 作成

    pool = wl_shm_create_pool(g_shm, fd, size);

    //クローズしてもアンマップはされない
    close(fd);

    *ppbuf = data;

    return pool;
}

/** イメージバッファ作成 */

static imagebuf *imagebuf_create(int width,int height)
{
    imagebuf *img;
    struct wl_shm_pool *pool;
    struct wl_buffer *buffer;
    void *buf;
    int size;

    size = width * 4 * height;

    //wl_shm_pool 作成

    pool = _create_shm_pool(size, &buf);
    if(!pool) return NULL;

    printf("create shm pool: ok\n");

    //wl_buffer 作成

    buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
        width * 4, WL_SHM_FORMAT_XRGB8888);

    if(buffer)
        printf("create buffer: ok\n");

    //imagebuf

    img = (imagebuf *)malloc(sizeof(imagebuf));
    if(!img) return NULL;

    img->pool = pool;
    img->buffer = buffer;
    img->data = buf;
    img->size = size;

    return img;
}

/** imagebuf 削除 */

static void imagebuf_destroy(imagebuf *p)
{
    if(p)
    {
        wl_buffer_destroy(p->buffer);
        wl_shm_pool_destroy(p->pool);
        munmap(p->data, p->size);

        free(p);
    }
}

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

static void _registry_global(
    void *data,struct wl_registry *registry,
    uint32_t id,const char *interface,uint32_t version)
{
    if(strcmp(interface, "wl_shm") == 0)
        g_shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
}

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

}

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

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

int main(void)
{
    struct wl_display *display;
    struct wl_registry *reg;
    imagebuf *img;

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

    reg = wl_display_get_registry(display);
    wl_registry_add_listener(reg, &g_reg_listener, NULL);

    wl_display_roundtrip(display);

    //イメージ作成

    img = imagebuf_create(100, 100);
    imagebuf_destroy(img);

    //

    wl_shm_destroy(g_shm);
    wl_registry_destroy(reg);

    wl_display_disconnect(display);

    return 0;
}
解説
後々使いやすいように、イメージデータとして imagebuf 構造体を作って、作成&削除できるようにしました。

Wayland でイメージバッファを作るには、wl_shm_pool_create_buffer()wl_buffer を作成します。
wl_buffer を作るには、wl_shm_pool が必要です。

wl_shm_poolwl_shm_create_pool() で作成できますが、作成にはファイルディスクリプタが必要です。
イメージバッファ用のファイルディスクリプタを得るために、任意のディレクトリに作業用のファイルを作る方法もありますが、shm_open() で POSIX 共有メモリオブジェクトを作成して取得する方法もあります。
今回は shm_open() を使います。
POSIX 共有メモリオブジェクトの作成
共有メモリ用のファイルディスクリプタを取得するために、shm_open() を使います。

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);

[!] librt のリンクが必要。

name は、最大 NAME_MAX (255) 文字の文字列で、先頭はスラッシュ (/) から始まり、以降は任意の文字列にします。
すでに存在する名前とかぶらないように、適当な名前を付けてください。
同じ名前がすでに存在した場合は、errnoEEXIST が入ります。

今回は一つのイメージしか作成しないので、名前は固定名でも問題ありませんが、
通常は複数作成すると思うので、作成するごとに数値のカウントを増やして、名前に付加する形にしています。

shm_open() の戻り値は、-1 で失敗、0 以上の場合は成功で、ファイルディスクリプタの値となります。

shm_unlink() で指定名のオブジェクトを削除します。
ファイルの unlink() と同じで、関数実行後もオブジェクトが使用されている間は存在していますが、アンマップされた時点で自動的に破棄されます。
なので、今回は作成直後に shm_unlink() して、解放処理を一つ省略しています。
wl_shm_pool の作成
wl_shm_pool は、共有メモリバッファ用のプール (溜める場所) です。
イメージ一つにつき一つ必要、というわけではなく、複数のイメージ用に大きなプールを作っておいて、そこから一部分を使う、という使い方も出来ます。

サイズ変更
shm_open() で作成した共有メモリオブジェクトは、O_CREAT を指定しているため、サイズが 0 となっています。
まずは、ftruncate(fd, size) でサイズを変更します。
今回は WL_SHM_FORMAT_XRGB8888 フォーマットにするので、1px = 4byte になるため、「幅×高さ×4 byte」 のサイズになります。

マッピング
次に、mmap() でメモリにマッピングします。

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

int munmap(void *addr, size_t length);

mmap() の戻り値は、成功すればバッファのポインタです。失敗時は、MAP_FAILED が返ります。

wl_shm_pool 作成
マッピングできたら、wl_shm_create_pool()wl_shm_pool を作成します。

struct wl_shm_pool *wl_shm_create_pool(struct wl_shm *wl_shm, int32_t fd, int32_t size);

その後、close(fd) でファイルディスクリプタをクローズしています。
mmap() されたバッファは、fd をクローズしてもアンマップされないので、解放処理の省略のため、ここでクローズしています。
wl_buffer の作成
wl_shm_pool が作成できたら、wl_shm_pool_create_buffer() でようやく Wayland のイメージバッファが作成できます。

struct wl_buffer *wl_shm_pool_create_buffer(struct wl_shm_pool *wl_shm_pool,
    int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format);

offset は、プール内のオフセット位置。一つのプールで複数の wl_buffer を作る場合は、その開始位置を指定する。
stride は、Y 1行のバイト数です。今回は width x 4 としています。
解放処理
wl_buffer_destroy(p->buffer);
wl_shm_pool_destroy(p->pool);
munmap(p->data, p->size);