Wayland - クリップボード

クリップボード
クリップボードのコピーと貼り付けを行います。

C : コピー
P : 貼り付け
R : クリップボード解放
ESC : 終了

> common1.h
> common1.c

$ cc -o test 12_clipboard.c -lwayland-client -lrt

<12_clipboard.c>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define __USE_GNU
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>

#include <linux/input.h>

#include "common1.h"

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

typedef struct
{
    wayland base;
    int quit_flag;
    struct pollfd poll[2];

    struct wl_data_device_manager *data_device_manager;
    struct wl_data_device *data_device;
    struct wl_data_source *data_source;
    struct wl_data_offer *data_offer;
    uint32_t data_device_manager_version;

    char *copy_text,
        *recv_buf;
    int text_size,
        recv_cursize,
        recv_bufsize;
}wayland_ex;

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


/** ソースデータ解放 */

static void _source_release(wayland_ex *p)
{
    if(p->data_source)
    {
        wl_data_source_destroy(p->data_source);
        p->data_source = NULL;
    }

    if(p->copy_text)
    {
        free(p->copy_text);
        p->copy_text = NULL;
    }
}


//========================
// wl_data_source
//========================


static void _data_source_target(void *data,
    struct wl_data_source *source, const char *mime_type)
{
    //D&D
}

/** データが要求された時 */

static void _data_source_send(void *data, struct wl_data_source *source,
    const char *mime_type, int32_t fd)
{
    wayland_ex *p = (wayland_ex *)data;

    if(!mime_type)
        close(fd);
    else
    {
        printf("$ data_source.send: %s\n", mime_type);

        write(fd, p->copy_text, p->text_size);
        close(fd);
    }
}

/** データソースが置き換わった時 */

static void _data_source_cancelled(void *data, struct wl_data_source *source)
{
    printf("$ data_source.cancelled: %p\n", source);

    _source_release((wayland_ex *)data);
}

void _data_source_dnd_drop_performed(void *data, struct wl_data_source *source)
{
    //D&D
}

void _data_source_dnd_finished(void *data, struct wl_data_source *source)
{
    //D&D
}

void _data_source_action(void *data, struct wl_data_source *source, uint32_t dnd_action)
{
    //D&D
}

static const struct wl_data_source_listener g_data_source_listener = {
    _data_source_target, _data_source_send, _data_source_cancelled,
    _data_source_dnd_drop_performed, _data_source_dnd_finished,
    _data_source_action
};


//========================
// wl_data_offer
//========================


/** 提供されている MIME タイプが通知される */

static void _data_offer_offer(void *data, struct wl_data_offer *offer, const char *type)
{
    printf("+ data_offer.offer: %s\n", type);
}

static void _data_offer_source_actions(void *data,
    struct wl_data_offer *offer, uint32_t source_actions)
{
    //D&D
}

static void _data_offer_action(void *data, struct wl_data_offer *offer, uint32_t dnd_action)
{
    //D&D
}

static const struct wl_data_offer_listener g_data_offer_listener = {
    _data_offer_offer, _data_offer_source_actions, _data_offer_action
};


//========================
// wl_data_device
//========================


static void _device_data_offer(void *data,
    struct wl_data_device *data_device, struct wl_data_offer *offer)
{
    printf("# data_device.data_offer: %p\n", offer);

    wl_data_offer_add_listener(offer, &g_data_offer_listener, data);
}

static void _device_enter(void *data, struct wl_data_device *data_device, uint32_t serial,
    struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *offer)
{
    //D&D
}

static void _device_leave(void *data, struct wl_data_device *data_device)
{
    //D&D
}

static void _device_motion(void *data, struct wl_data_device *data_device,
    uint32_t time, wl_fixed_t x, wl_fixed_t y)
{
    //D&D
}

static void _device_drop(void *data, struct wl_data_device *data_device)
{
    //D&D
}

/** クリップボード用の wl_data_offer が通知される */

static void _device_selection(void *data,
    struct wl_data_device *data_device, struct wl_data_offer *offer)
{
    wayland_ex *p = (wayland_ex *)data;

    printf("# data_device.selection: %p\n", offer);

    //前回の data_offer は破棄する

    if(p->data_offer)
        wl_data_offer_destroy(p->data_offer);

    //クリップボードにデータがない場合は NULL

    p->data_offer = offer;
}

static const struct wl_data_device_listener g_data_device_listener = {
    _device_data_offer, _device_enter, _device_leave,
    _device_motion, _device_drop, _device_selection
};


//========================
// コピー/貼り付け処理
//========================


/** コピー */

static void _copy_text(wayland_ex *p,const char *text,uint32_t serial)
{
    printf("@ copy text: %s\n", text);

    _source_release(p);

    //内部データに保持 (NULL 文字なし)

    p->text_size = strlen(text);

    p->copy_text = (char *)malloc(p->text_size);
    memcpy(p->copy_text, text, p->text_size);

    //このクライアントがデータを持っていることを要求

    p->data_source = wl_data_device_manager_create_data_source(p->data_device_manager);

    wl_data_source_offer(p->data_source, "text/plain;charset=utf-8");
    wl_data_source_offer(p->data_source, "UTF8_STRING");

    wl_data_source_add_listener(p->data_source, &g_data_source_listener, p);

    wl_data_device_set_selection(p->data_device, p->data_source, serial);
}

/** 貼り付け */

static void _paste(wayland_ex *p)
{
    int fd[2];

    if(!p->data_offer)
    {
        printf("@ paste: none data\n");
        return;
    }

    //データ受信中

    if(p->poll[1].fd != -1) return;

    //データの受信を要求

    if(pipe2(fd, O_CLOEXEC) == -1) return;

    wl_data_offer_receive(p->data_offer, "text/plain;charset=utf-8", fd[1]);
    close(fd[1]);

    p->poll[1].fd = fd[0];
    p->recv_cursize = 0;
}

/** 貼り付けのデータ受信 */

static void _data_recv_handle(wayland_ex *p,int events)
{
    int fd,ret = 0;

    fd = p->poll[1].fd;

    printf("@@ poll: ");
    if(events & POLLIN) printf("POLLIN ");
    if(events & POLLHUP) printf("POLLHUP ");
    printf("\n");

    //受信するデータがある場合

    if(events & POLLIN)
    {
        //最後に NULL 文字を追加するので、1byte 残す

        ret = read(fd, p->recv_buf + p->recv_cursize, p->recv_bufsize - p->recv_cursize - 1);

        p->recv_cursize += ret;

        printf("@@ recv: %d (%d/%d)\n", (int)ret, p->recv_cursize, p->recv_bufsize);
    }

    //

    if(ret == 0)
    {
        //データ終了

        close(fd);
        p->poll[1].fd = -1;

        //NULL 文字はデータに含まれていないので追加
        p->recv_buf[p->recv_cursize] = 0;

        printf("@@ paste text >\n%s\n", p->recv_buf);
    }
    else
    {
        //データが続く場合、常に 1KB 以上の空き容量を作っておく

        if(p->recv_bufsize - p->recv_cursize < 1024)
        {
            p->recv_bufsize += 1024;
            p->recv_buf = (char *)realloc(p->recv_buf, p->recv_bufsize);
        }
    }
}


//========================
// wl_keyboard
//========================


static void _keyboard_keymap(void *data, struct wl_keyboard *keyboard,
    uint32_t format, int32_t fd, uint32_t size)
{
}

static void _keyboard_enter(void *data, struct wl_keyboard *keyboard,
    uint32_t serial, struct wl_surface *surface, struct wl_array *keys)
{
}

static void _keyboard_leave(void *data, struct wl_keyboard *keyboard,
    uint32_t serial, struct wl_surface *surface)
{
}

static void _keyboard_key(void *data, struct wl_keyboard *keyboard,
    uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
{
    wayland_ex *p = (wayland_ex *)data;
    char m[64];

    if(state != WL_KEYBOARD_KEY_STATE_PRESSED) return;

    switch(key)
    {
        //コピー
        case KEY_C:
            snprintf(m, 64, "copytext%u", serial);
            _copy_text(p, m, serial);
            break;
        //貼り付け
        case KEY_P:
            _paste(p);
            break;
        //クリップボード解除
        case KEY_R:
            if(p->data_source)
            {
                wl_data_device_set_selection(p->data_device, NULL, serial);
                _source_release(p);
            }
            break;
        //ESC キーで終了
        case KEY_ESC:
            p->quit_flag = 1;
            break;
    }
}

static void _keyboard_modifiers(void *data, struct wl_keyboard *keyboard,
    uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
{
}

static void _keyboard_repeat_info(void *data, struct wl_keyboard *keyboard,
    int32_t rate, int32_t delay)
{
}

static const struct wl_keyboard_listener g_keyboard_listener = {
    _keyboard_keymap, _keyboard_enter, _keyboard_leave, _keyboard_key,
    _keyboard_modifiers, _keyboard_repeat_info
};


//========================
// wl_registry
//========================


static void _registry_global(
    void *data,struct wl_registry *reg,uint32_t id,const char *itf,uint32_t ver)
{
    wayland_ex *p = (wayland_ex *)data;

    if(strcmp(itf, "wl_data_device_manager") == 0)
    {
        if(ver >= 3) ver = 3;

        p->data_device_manager = wl_registry_bind(reg, id, &wl_data_device_manager_interface, ver);
        p->data_device_manager_version = ver;
    }
}

//========================


/** イベントループ */

static void _event_loop(wayland_ex *p)
{
    //display 用
    p->poll[0].fd = wl_display_get_fd(p->base.display);
    p->poll[0].events = POLLIN;

    //貼り付けデータ取得用 (fd が負の値だと無視される)
    p->poll[1].fd = -1;
    p->poll[1].events = POLLIN;

    //

    while(!p->quit_flag)
    {
        wl_display_flush(p->base.display);

        if(poll(p->poll, 2, -1) < 0) break;

        //display にイベントがある

        if(p->poll[0].revents & POLLIN)
            wl_display_dispatch(p->base.display);

        //データ受信

        if(p->poll[1].revents)
            _data_recv_handle(p, p->poll[1].revents);
    }
}

/** 破棄 */

static void _data_destroy(wayland_ex *p)
{
    free(p->recv_buf);

    if(p->data_offer)
        wl_data_offer_destroy(p->data_offer);

    _source_release(p);

    if(p->data_device_manager_version >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION)
        wl_data_device_release(p->data_device);
    else
        wl_data_device_destroy(p->data_device);

    wl_data_device_manager_destroy(p->data_device_manager);
}

/** メイン */

int main(void)
{
    wayland_ex *wl;
    window *win;

    wl = (wayland_ex *)wayland_new(sizeof(wayland_ex));

    wl->base.init_flags = INIT_FLAGS_SEAT | INIT_FLAGS_KEYBOARD;
    wl->base.handle_registry_global = _registry_global;
    wl->base.keyboard_listener = &g_keyboard_listener;

    wayland_init(WAYLAND_PTR(wl));

    //wl_data_device

    wl->data_device = wl_data_device_manager_get_data_device(
        wl->data_device_manager, wl->base.seat);

    wl_data_device_add_listener(wl->data_device, &g_data_device_listener, wl);

    //貼り付け用受信バッファ

    wl->recv_buf = (char *)malloc(1024);
    wl->recv_bufsize = 1024;

    //ウィンドウ

    win = window_create(WAYLAND_PTR(wl), 256, 256);

    imagebuf_fill(win->img, 0xff0000);
    window_update(win);

    //

    _event_loop(wl);

    //解放

    window_destroy(win);

    _data_destroy(wl);

    wayland_destroy(WAYLAND_PTR(wl));

    return 0;
}
解説
クリップボードと D&D は、同じインターフェイスを使って操作します。
wl_data_device_manager, wl_data_devie, wl_data_source, wl_data_offer の4つのオブジェクトを使います。
wl_data_device_manager
まずは、wl_data_device_manager をバインドします。
これは、wl_data_devicewl_data_source を作成するのに使います。

if(strcmp(itf, "wl_data_device_manager") == 0)
{
    if(ver >= 3) ver = 3;

    p->data_device_manager = wl_registry_bind(reg, id, &wl_data_device_manager_interface, ver);
    p->data_device_manager_version = ver;
}

2017/10 現在の最新バージョンは 3 です。
wl_data_device の破棄時に、destroy/release どちらを使うのかを判断するのにバージョンが必要なので、記録しています。
wl_data_device の取得
次に、wl_data_device_manager_get_data_device() で、wl_data_device を作成します。

## wl_data_device 取得

struct wl_data_device *wl_data_device_manager_get_data_device(
    struct wl_data_device_manager *wl_data_device_manager, struct wl_seat *seat);

## ハンドラ設定

int wl_data_device_add_listener(struct wl_data_device *wl_data_device,
    const struct wl_data_device_listener *listener, void *data);

wl_data_device を取得するには、wl_data_device_managerwl_seat が必要です。

ただし、wl_registry の global イベント内では、wl_data_device_manager の前に wl_seat が来るとは限りません。
実際、weston/GNOME 上では、wl_data_device_managerwl_seat の順で来ます。

そのため、wl_data_device_manager のバインドと同時に wl_data_device を作成できる可能性は低いので、wl_seat を作成した時か、すべての global イベントが終了した時に、wl_data_device を取得する必要があります。
wl_data_device のイベントハンドラ
void (*data_offer)(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id);

void (*enter)(void *data, struct wl_data_device *wl_data_device, uint32_t serial,
    struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id);

void (*leave)(void *data, struct wl_data_device *wl_data_device);

void (*motion)(void *data, struct wl_data_device *wl_data_device,
    uint32_t time, wl_fixed_t x, wl_fixed_t y);

void (*drop)(void *data, struct wl_data_device *wl_data_device)

void (*selection)(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id);

data_offer と selection 以外は、D&D 時に使われるので、今回は説明を省略します。

selection イベント
Wayland では、クリップボードのことを 「selection」 と呼びます。
サーバーから、現在クリップボードにあるデータの種類を得るための wl_data_offer が送られてきます。

クライアントが管理するウィンドウがアクティブになる直前と、
クライアントのウィンドウがアクティブな状態で新しいクリップボードデータが設定された時に来ます。

具体的には、以下のような時に来ます。

  • プログラムを起動して、最初のウィンドウがアクテイブになった時。
  • 他のウィンドウがアクティブな状態で、クライアントのウィンドウがアクティブになった時。
  • クライアントのウィンドウがアクティブな状態で、クライアント内でテキストをコピーしたりして、新しいクリップボードデータが設定された時。

これにより、現在クリップボードにデータがあるかないか、また、データがある場合、クリップボードのデータの種類の取得が行えます。

送られてきた wl_data_offer のポインタが NULL の場合は、現在クリップボードにデータがない状態です。
NULL 以外の場合、クリップボードにデータが存在します。

なお、wl_data_offer のポインタは、次の selection イベントで新しいポインタか NULL が送られてくるまで有効です。
このイベントが来た場合、前回取得した wl_data_offerwl_data_offer_destroy() で破棄する必要があります。
また、プログラムの終了時に、wl_data_offer が取得されている状態なら、破棄します。

data_offer イベント
サーバー側で新しい wl_data_offer が作成された時に来ます。

新しいクリップボードデータが設定された時や、
クリップボードデータが存在する状態でウィンドウがアクティブになる直前にも来ます。

クリップボードの場合は、このイベントの直後に、同じポインタ値で selection イベントが送られてきます。
また、wl_data_offer の offer イベントも送信されます。

このイベントは、基本的に、wl_data_offer のハンドラ設定を行うために用意されています。
wl_data_offer_add_listener() を使ってイベントハンドラを設定することで、クリップボードのデータの種類を MIME タイプで取得できます。
wl_data_offer のイベントハンドラ
wl_data_offer のイベントでは、ソースデータの情報が送られてきます。

void (*offer)(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type);

void (*source_actions)(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions);

void (*action)(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action);

offer 以外は、D&D で使います。

offer イベント
ソース側がデータとして送信可能な MIME タイプが、一つずつ通知されます。
MIME タイプは文字列で指定され、UTF-8 のテキストなら、"text/plain;charset=utf-8" となります。

ソースが一つのデータで複数の MIME タイプに対応している場合は、データを取得する側が取得したい MIME タイプを要求して、ソース側がその形式でデータを送信する、という形になります。
実際の動作を見てみる
ここまでで、クライアントが受け取るイベントを確認してみます。
起動時、またはウィンドウがアクティブになった時に以下のイベントが来ます。

#====== クリップボードデータがない時

# device_data.selection: (nil)

#====== クリップボードデータがある時 (GNOME)

# data_device.data_offer: 0x55818da6a550
+ data_offer.offer: TIMESTAMP
+ data_offer.offer: TARGETS
+ data_offer.offer: MULTIPLE
+ data_offer.offer: SAVE_TARGETS
+ data_offer.offer: text/plain;charset=utf-8
+ data_offer.offer: UTF8_STRING
+ data_offer.offer: STRING
# data_device.selection: 0x55818da6a550

selection と data_offer の値は、wl_data_offer のポインタ値です。

クリップボードデータがない時は、selection で NULL が送られてきます。

データがある時は、data_offer > offer > selection の順でイベントが来ます。
クリップボードデータは、offer イベントで送られてきた MIME タイプのいずれかで取得できることになります。

データが UTF-8 テキストなら、本来は "text/plain;charset=utf-8" ですが、他にもいくつかタイプが登録されています。

TIMESTAMP, TARGETS, MULTIPLE, SAVE_TARGETS などは、X11 で使われていた名前です。
Wayland 上で X11 プログラムを動作させるための互換として登録されているので、Wayland プログラムの場合、これらは無視して構いません。

ただし、UTF8_STRING は使うので、それは後述します。
クリップボードについて
クライアントのデータの保持について
クリップボードにデータをコピーしたい場合、データを持つクライアントは、自身で常にデータを保持しておく必要があります。

データは複数形式でやりとりできるようになっているので、例えば、クリップボードデータを持っているクライアントが、画像のデータを保持していて、BMP, PNG, JPEG のいずれかでデータを渡せるようになっている場合、他のクライアントはその中のいずれかを選択して、データを持っているクライアントにデータの送信を要求します。
そうすると、データを持っているクライアントは、要求された形式にエンコードしてから、データを送信します。

このように、データが要求された時に、要求された形式にエンコードして送信する必要があるため、クリップボードデータを持つ側は、常に素のデータを保持する必要があります。

クリップボードマネージャ
上記のような動作の場合、データを持つクライアントが終了してしまうと、データ自体が存在しなくなってしまうため、その後にデータを取得することはできなくなります。

クライアントが終了してもクリップボードデータを維持したい場合は、クリップボードマネージャが必要です。
もしくは、Wayland サーバー自体がその機能を実装している場合もあるかもしれません。
(weston は、簡易的にクリップボードマネージャの機能を内蔵しています)

クリップボードマネージャが稼働している場合、クライアントがクリップボードデータをセットした直後に、クリップボードマネージャからデータの送信が要求されます。
クリップボードマネージャは、クリップボードに新しいデータがセットされたら、そのデータをクライアントから取得し、内部に保持します。
データを持つクライアントが動いている間は、クライアント自身がクリップボードデータのやりとりを行いますが、クライアントが終了すると、クリップボードマネージャが、自身で保持しているデータを元にクリップボードデータを再セットします。
以降は、クリップボードマネージャがデータの所持者となって、やりとりを行います。
これで、クライアント終了後も、テキストなどの貼り付けが行えます。

ただし、クリップボードマネージャが、複数形式のデータに対応するかどうかはわかりません。
テキストデータなら、UTF-8 で保存しておけば大抵の形式に変換できるので問題ありませんが、他のデータがどの形式で保存されるかはわかりません。
クリップボードマネージャは、テキストなど、一般的なデータのみに対応すると思っておいてください。
クリップボードにデータをコピーする
## wl_data_source を作成

struct wl_data_source *wl_data_device_manager_create_data_source(
    struct wl_data_device_manager *wl_data_device_manager);

## wl_data_source 破棄

void wl_data_source_destroy(struct wl_data_source *wl_data_source);

## MIME タイプ追加

void wl_data_source_offer(struct wl_data_source *wl_data_source, const char *mime_type);

## wl_data_source のハンドラ設定

int wl_data_source_add_listener(struct wl_data_source *wl_data_source,
    const struct wl_data_source_listener *listener, void *data);

## クリップボードのデータとしてセット

void wl_data_device_set_selection(struct wl_data_device *wl_data_device,
    struct wl_data_source *source, uint32_t serial);

まず、wl_data_device_manager_create_data_source() で、wl_data_source を作成します。
これは、クリップボードのソースデータを管理するオブジェクトです。
新しいデータを設定するたびに作り直します。

次に、wl_data_source_offer() で、対応する MIME タイプを追加します。
一つのデータで複数の形式に対応するなら、複数回呼びます。

wl_data_source_add_listener() で、データをやりとりするためのハンドラを設定します。

最後に、wl_data_device_set_selection() で、wl_data_source を、新しいクリップボードのデータとしてセットします。
NULL を指定すると、クリップボードから解除されます。ただし、クリップボードが空になるわけではありません。
serial は、ポインタ/キーボード操作などで、最後に呼ばれたハンドラのシリアル値です。

X11 プログラム内での貼り付け
xwayland を使って、Wayland 上で X11 プログラムを動かしている場合、
X11 で使われる名前 ("UTF8_STRING" など) も MIME タイプとして設定しておかないと、X11 プログラム内でテキストの貼り付けが行なえません。

"text/plain;charset=utf-8" だけでは、UTF-8 テキストとして認識されないので、注意が必要です。
(Wayland プログラム同士の場合は、普通の MIME タイプでやりとりできます)

しかし、ウィンドウの見た目では、Wayland と X11 どちらで起動しているのかはわかりません。
GTK+3、Qt5 以降は Wayland に対応しているので、それ以前を使って作られている場合は、X11 で起動していると思っていいでしょう。

クリップボードデータの解放
wl_data_device_set_selection() で、引数の wl_data_source * に NULL を指定すると、クライアントが設定したクリップボードデータを解放することができます。

これは、クリップボードを空にするという意味ではなく、このクライアントがデータの所有権を放棄するという意味です。

クリップボードマネージャが動いていない場合は、クリップボードデータは空になりますが、クリップボードマネージャがある場合、データの所有権をクリップボードマネージャに移すことになります。
データを保持しているクライアントがそのデータを放棄した場合、クリップボードマネージャは、内部で保持しているデータを新しいクリップボードデータとしてセットします。

結果として、テキストなどの場合は、クライアントが持っていたデータと同じ内容を、今度はクリップボードマネージャを通して貼り付ることができるようになります。

<コピー>
@ copy text: copytext7617
# data_device.data_offer: 0x56122b247840
+ data_offer.offer: text/plain;charset=utf-8
+ data_offer.offer: UTF8_STRING
# data_device.selection: 0x56122b247840
$ data_source.send: text/plain;charset=utf-8
↑ クリップボードマネージャからの要求

<解放>
# data_device.selection: (nil)

<クリップボードマネージャがデータをセット>
# data_device.data_offer: 0x56122b247890
+ data_offer.offer: TIMESTAMP
+ data_offer.offer: TARGETS
+ data_offer.offer: MULTIPLE
+ data_offer.offer: text/plain;charset=utf-8
+ data_offer.offer: UTF8_STRING
+ data_offer.offer: COMPOUND_TEXT
+ data_offer.offer: TEXT
+ data_offer.offer: STRING
+ data_offer.offer: text/plain;charset=utf-8
+ data_offer.offer: text/plain
# data_device.selection: 0x56122b247890

解放すると、一旦 selection イベントで NULL が来ますが、その直後に、クリップボードマネージャがクリップボードデータを再セットしています。
クライアントプログラムの終了時も同じような動作になります。
wl_data_source のイベントハンドラ
void (*target)(void *data, struct wl_data_source *wl_data_source, const char *mime_type);

void (*send)(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd);

void (*cancelled)(void *data, struct wl_data_source *wl_data_source);

void (*dnd_drop_performed)(void *data, struct wl_data_source *wl_data_source);

void (*dnd_finished)(void *data, struct wl_data_source *wl_data_source);

void (*action)(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action);

クリップボードのソース用として使う場合、dnd_drop_performed 以降は使いませんが、ちゃんと使用するバージョンに合わせてすべてのハンドラを設定しないと、GNOME では以下のエラーが出ました。
listener function for opcode 5 of wl_data_source is NULL

send イベント
クリップボードマネージャや、他のクライアントからデータを要求された時に呼ばれます。

クライアントは、指定された MIME タイプの形式で、データを送信する必要があります。
渡された fd に対して、write() で書き込みを行って、最後に close() で閉じます。

文字列の場合、NULL 文字は付けずに送信したほうが良いでしょう。
受け取るクライアント側は、NULL 文字が付いていないと想定してデータを扱う必要があります。

cancelled イベント
wl_data_source のデータがキャンセルされた。
クリップボードの場合は、クライアントがセットしたデータが別のクリップボードデータに置き換わった時、元のデータの wl_data_source ポインタが送られてきます。

クライアントが保持している wl_data_source と、クリップボードのデータを解放する必要があります。

コピー時の動作
@ copy text: copytext7349
# data_device.data_offer: 0x55818da6a840
+ data_offer.offer: text/plain;charset=utf-8
+ data_offer.offer: UTF8_STRING
# data_device.selection: 0x55818da6a840
$ data_source.send: text/plain;charset=utf-8

wl_data_device_set_selection() でクリップボードデータがセットされたら、まず、wl_data_device#data_offer イベントが来ます。
その後、wl_data_offer#offer イベントで、設定された MIME タイプが列挙されます。
次に、wl_data_device#selection イベントで、クリップボード用の wl_data_offer ポインタが渡されます。

その後、wl_data_source#send イベントでデータが要求されていますが、これはクリップボードマネージャからの要求です。
クリップボードからの貼り付け
クリップボードからデータを取得するには、wl_data_device の selection イベントで渡される、wl_data_offer のポインタが必要です。
ポインタが NULL の場合は、クリップボードデータが存在していないということになります。

なお、データを受信するためには、read() を使って fd から読み込む必要がありますが、今回は、イベントループ内に組み込んで、poll で入力を待って読み込む形にしてあります。

データを要求する
void wl_data_offer_receive(struct wl_data_offer *wl_data_offer, const char *mime_type, int32_t fd);

wl_data_offer_receive() を使うと、クリップボードデータを持っている対象に対して、指定した MIME タイプでデータを送信するように要求します。
fd は、書き込み側のファイルディスクリプタを指定します。

なお、wl_data_offer に登録されていない MIME タイプを指定した場合、判定などは行われずに、そのまま send イベントが送信されるので、データを送信する側が MIME タイプの判別を行う必要があります。
今回の場合は、受信するデータを UTF-8 テキストに限定していますが、本来は、wl_data_offer#offer イベントで、受信可能な MIME タイプを記録しておいて、受信側でタイプを選別するべきです。

データを受信する場合は、パイプを使って fd を2つ作成し、書き込み用の fd を wl_data_offer_receive() に渡して、すぐに close() で閉じます。
その後、読み込み用の fd を使って、読み込みを行い、閉じられたら転送を終了します。

pipe に O_CLOEXEC フラグを付けるために、pipe2() 関数を使っています。
weston でも GTK+ でもこのフラグは付けられているので、指定した方が良さそうです。

イベントループ
今回は、poll() を使って、Wayland のイベントとクリップボードデータの受信を待っています。
select や epoll を使っても構いません。

クリップボードのデータの場合は、wl_data_offer_receive() の後、wl_display_dispatch() を実行すると、その後すぐに受信することはできますが、D&D データの場合、イベントを順番通り処理しつつ受信を行わないと、正しく処理が行えないので、データの受信はイベントループ内に組み込む必要があります。

wl_display_get_fd() で、Wayland ディスプレイ用の fd を取得できます。
これを使うと、イベントが存在する時に入力がある状態となり、イベントが起こるまで待つことができます。

なお、poll を行う前に、wl_display_flush() でクライアントのデータをサーバーに送信しないと、イベントが起こらないので、永遠に待つことになります。

データの受信
クリップボードデータ受信では、データが入力された時と、書き込み側で fd が閉じられた状態の時に poll を抜けるので、その時にデータの受信&終了処理を行います。

入力データがある時は、recv() で受信します。

POLLHUP が ON の場合は、書き込み側の fd が閉じられた状態です。
書き込み側が閉じられても、読み込み側の fd をクローズしない限り、読み込みはそのまま継続できます。

書き込み側が閉じられて、入力データがもうない場合は、データが終了したということになります。
close() で fd を閉じて、受信したデータを表示しています。
今回はデータを文字列として扱うので、NULL 文字を最後に追加しています。