Wayland - D&D

D&D
D&D 処理を行います。

ウィンドウ内で、左ボタンでドラッグすると、D&D が開始されます。
UTF-8 テキストをドロップ可能なウィンドウ上でボタンを離すと、テキストがドロップされます。

サンプルプログラムのウィンドウ内にドロップして、D&D を受け取る側の動作を確認することもできます。

上半分の赤い部分にドロップした場合、COPY アクションとして受け付けます。
下半分の青い部分にドロップした場合、ASK アクションとして受け付けます (最終的には COPY で処理)

※ マウスカーソルの変更処理は行っていません。

> common2.h
> common2.c

$ cc -o test 13_dnd.c common2.c -lwayland-client -lrt

<13_dnd.c>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

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

#include <linux/input.h>

#include "common2.h"

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

typedef struct
{
    wayland base;
    window *win;

    int recv_bufsize,
        recv_cursize;
    char *recv_buf;

    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,
        device_enter_serial,
        source_actions,
        dnd_action;
}wayland_ex;

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


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

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

/** アクション表示 */

static void _put_action(uint32_t f)
{
    if(f == WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE)
        printf(" NONE");
    else
    {
        if(f & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
            printf(" COPY");

        if(f & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
            printf(" MOVE");

        if(f & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
            printf(" ASK");
    }
}

/** ドロップ先の情報設定 */

static void _target_enter_leave(wayland_ex *p,int y)
{
    int type;

    type = ((y >> 8) >= p->win->height / 2);

    //受け取る側が対応可能なアクションを指定 (ver 3 以降)

    if(p->data_device_manager_version >= WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION)
    {
        if(type == 0)
        {
            //上半分: COPY,MOVE

            wl_data_offer_set_actions(p->data_offer,
                WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY
                    | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE,
                WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
        }
        else
        {
            //下半分: ASK

            wl_data_offer_set_actions(p->data_offer,
                WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK,
                WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK);
        }
    }

    //受け入れる MIME タイプ (NULL で D&D を受け入れない)

    wl_data_offer_accept(p->data_offer, p->device_enter_serial, "text/plain;charset=utf-8");
}

/** D&D データ受信 */

static void _poll_recv_handle(wayland *wl,int fd,int events)
{
    wayland_ex *p = (wayland_ex *)wl;
    ssize_t ret = 0;

    if(events & POLLIN)
    {
        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)
    {
        //データ終了

        wayland_poll_delete(wl, fd);

        p->recv_buf[p->recv_cursize] = 0;

        printf("@@ recv >\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);
        }
    }
}

/** データ送信 */

static void _poll_write_handle(wayland *wl,int fd,int events)
{
    if((events & POLLOUT) && !(events & POLLERR))
        write(fd, "dnd_test", 8);

    wayland_poll_delete(wl, fd);
}


//====================================
// wl_data_source (D&D 元データ管理)
//====================================


/** D&D 先で、enter/move イベント時に MIME タイプが指定された時 */

static void _data_source_target(void *data,
    struct wl_data_source *source, const char *mime_type)
{
    //D&D 先がデータを受け入れない場合、NULL が渡される

    printf("$ data_source.target: %s\n", (mime_type)? mime_type: "NULL");
}

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

static void _data_source_send(void *data, struct wl_data_source *source,
    const char *mime_type, int32_t fd)
{
    printf("$ data_source.send: %s\n", mime_type);

    wayland_poll_add(WAYLAND_PTR(data), fd, POLLOUT, _poll_write_handle);
}

/** キャンセル時 */

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

    _source_release((wayland_ex *)data);
}

/** ドロップ先が drop イベントを受け取った時 */

void _data_source_dnd_drop_performed(void *data, struct wl_data_source *source)
{
    printf("$ data_source.dnd_drop_performed\n");
}

/** D&D 処理が全て終了した */

void _data_source_dnd_finished(void *data, struct wl_data_source *source)
{
    printf("$ data_source.dnd_finished\n");

    _source_release((wayland_ex *)data);
}

/** サーバーが選択したアクションが通知される (D&D ソース向け) */

void _data_source_action(void *data, struct wl_data_source *source, uint32_t dnd_action)
{
    printf("$ data_source.action:");
    _put_action(dnd_action);
    printf("\n");
}

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 (D&D 元の情報取得)
//===================================


/** D&D 元で提供されている MIME タイプの通知 */

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

/** D&D 元が対応可能なアクションの通知 */

static void _data_offer_source_actions(void *data,
    struct wl_data_offer *offer, uint32_t source_actions)
{
    printf("+ data_offer.source_actions:");
    _put_action(source_actions);
    printf("\n");

    ((wayland_ex *)data)->source_actions = source_actions;
}

/** サーバーが選択したアクションが通知される (D&D 先向け) */

static void _data_offer_action(void *data, struct wl_data_offer *offer, uint32_t dnd_action)
{
    printf("+ data_offer.action:");
    _put_action(dnd_action);
    printf("\n");

    ((wayland_ex *)data)->dnd_action = dnd_action;
}

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


//===================================
// wl_data_device (D&D 受け入れる側)
//===================================


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);
}

/** D&D ポインタがウィンドウに入った時 */

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)
{
    wayland_ex *p = (wayland_ex *)data;

    printf("# data_device.enter: (%d, %d) %p\n", x >> 8, y >> 8, offer);

    p->data_offer = offer;
    p->device_enter_serial = serial;

    _target_enter_leave(p, y);
}

/** D&D ポインタがウィンドウ外に出た or D&D が終了した時 */

static void _device_leave(void *data, struct wl_data_device *data_device)
{
    wayland_ex *p = (wayland_ex *)data;

    printf("# data_device.leave\n");

    if(p->data_offer)
    {
        wl_data_offer_destroy(p->data_offer);
        p->data_offer = NULL;
    }
}

/** ポインタがウィンドウ内で移動した時 */

static void _device_motion(void *data, struct wl_data_device *data_device,
    uint32_t time, wl_fixed_t x, wl_fixed_t y)
{
    printf("# data_device.motion: (%d, %d)\n", x >> 8, y >> 8);

    _target_enter_leave((wayland_ex *)data, y);
}

/** ユーザーがボタンを離して、ドロップを受け入れる時 */

static void _device_drop(void *data, struct wl_data_device *data_device)
{
    wayland_ex *p = (wayland_ex *)data;
    int fd[2];

    printf("# data_device.drop\n");

    //データ受信開始

    if(pipe2(fd, O_CLOEXEC) != -1)
    {
        wl_data_offer_receive(p->data_offer, "text/plain;charset=utf-8", fd[1]);
        wl_display_flush(p->base.display);

        close(fd[1]);

        wayland_poll_add(WAYLAND_PTR(p), fd[0], POLLIN, _poll_recv_handle);

        p->recv_cursize = 0;
    }

    //ASK の場合、ユーザーが選択したアクションをセット

    if(p->dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK
        && p->data_device_manager_version >= WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION)
    {
        wl_data_offer_set_actions(p->data_offer,
            WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY,
            WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
    }

    //D&D 終了を通知する

    if(p->data_device_manager_version >= WL_DATA_OFFER_FINISH_SINCE_VERSION)
        wl_data_offer_finish(p->data_offer);
}

static void _device_selection(void *data,
    struct wl_data_device *data_device, struct wl_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
};


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


/** D&D 開始 */

static void _start_drag(wayland_ex *p,uint32_t serial)
{
    //ソース準備

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

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

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

    //D&D 元が対応可能なアクションをセット

    if(p->data_device_manager_version >= WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION)
    {
        wl_data_source_set_actions(p->data_source,
            WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY
                | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE
                | WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK);
    }

    //D&D 開始

    wl_data_device_start_drag(p->data_device,
        p->data_source, p->win->surface, NULL, serial);
}


//========================
// wl_pointer
//========================


static void _pointer_enter(void *data, struct wl_pointer *pointer,
    uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y)
{
    printf("& pointer.enter\n");
}

static void _pointer_leave(void *data, struct wl_pointer *pointer,
    uint32_t serial, struct wl_surface *surface)
{
    printf("& pointer.leave\n");
}

static void _pointer_motion(void *data, struct wl_pointer *pointer,
    uint32_t time, wl_fixed_t x, wl_fixed_t y)
{
    printf("& pointer.motion: (%d, %d)\n", x >> 8, y >> 8);
}

static void _pointer_button(void *data, struct wl_pointer *pointer,
    uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
{
    wayland_ex *p = (wayland_ex *)data;

    if(state == WL_POINTER_BUTTON_STATE_PRESSED)
    {
        if(button == BTN_MIDDLE)
            //中ボタンで終了
            p->base.loop_flag = 0;
        else if(button == BTN_LEFT)
            _start_drag(p, serial);
    }
}

static void _pointer_axis(void *data, struct wl_pointer *pointer,
    uint32_t time, uint32_t axis, wl_fixed_t value)
{
}

static void _pointer_frame(void *data, struct wl_pointer *pointer)
{
}

static void _pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source)
{
}

static void _pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis)
{
}

static void _pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete)
{
}

static const struct wl_pointer_listener g_pointer_listener = {
    _pointer_enter, _pointer_leave, _pointer_motion, _pointer_button,
    _pointer_axis, _pointer_frame, _pointer_axis_source, _pointer_axis_stop, _pointer_axis_discrete
};


//========================
// 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 _data_destroy(wayland_ex *p)
{
    if(p->recv_buf)
        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_POINTER;
    wl->base.handle_registry_global = _registry_global;
    wl->base.pointer_listener = &g_pointer_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;

    //ウィンドウ

    wl->win = win = window_create(WAYLAND_PTR(wl), 256, 300);

    imagebuf_fillh(win->img, 0, 150, 0xff0000);
    imagebuf_fillh(win->img, 150, 150, 0x0000ff);
    window_update(win);

    //

    wayland_loop(WAYLAND_PTR(wl));

    //解放

    window_destroy(win);

    _data_destroy(wl);

    wayland_destroy(WAYLAND_PTR(wl));

    return 0;
}
解説 (D&D を受け取る側)
まず、D&D を受け取る側の処理を説明していきます。

D&D では、クリップボードの時と同じで、wl_data_* のオブジェクトを使用します。
初期化等については、前回の説明を見てください。

なお、wl_data_device_manager のバージョンが 3 以降かそれ未満の場合で、行う処理が多少異なってきます。
ver 3 では、アクションの概念が追加されているので、現状ではほとんどのサーバーが ver 3 以降に対応していると思われます。
ドロップ時のアクションについて
先に、ドロップ時のアクションについて説明します。

D&D では、「COPY (コピー)、MOVE (移動)、ASK (ユーザーに尋ねる)」 の3つのアクションを行うことができます。

COPYファイルや文字列などのコピー。D&D 元のデータはそのまま残ります。
MOVEドロップ先にファイルなどを移動する。移動元のデータは削除されます。
ASKドロップされた時に、ドロップ先でメニューなどを表示して、ユーザーにアクションを選択させる。

アクションは、D&D ソース側とドロップ先のそれぞれで、対応可能なものを指定する必要があります。
※ アクションの設定は、ver 3 以降の場合に行います。

ソース側は、そのデータが対応可能なアクションを、D&D 開始時に一回だけ設定します。
ドロップ先では、ポインタの位置によって対応するクライアントやウィンドウが異なるため、その都度、受け取る時に対応可能なアクションを設定します。

ドロップ先が変わると、受け取る側のアクションが変更される場合があるため、最終的に実行するアクションは、サーバーが決定します。

フラグ値
アクションは、enum wl_data_device_manager_dnd_actions の列挙型で、フラグ値になっているため、複数指定ができます。

WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE = 0
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY = 1
WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE = 2
WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK  = 4

NONE は、D&D を受け付けない時などに設定されます。
wl_data_device のイベントハンドラ
wl_data_device のイベントハンドラで、D&D を受け取る側の処理を行います。

void (*data_offer)(void *data, struct wl_data_device *data_device, struct wl_data_offer *id);

void (*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 *id);

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

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

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

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

selection は、クリップボードでしか使いません。

data_offer イベント
クリップボードでも使いましたが、D&D の場合は、wl_data_device#enter イベントの直前に来ます。
クリップボードの時と同じように、ここでは wl_data_offer のイベントハンドラを設定します。

enter イベント
D&D 中に、クライアントが管理するウィンドウ内にポインタが入った時に来ます。
D&D 元の情報を取得したりデータを受信するための wl_data_offer ポインタが送られてくるので、ここでそのポインタを保持しておきます。

leave イベント
D&D ポインタが、enter で入ったウィンドウの外に出た時や、D&D 処理が終了した時に来ます。
ここでは、enter 時に保持した wl_data_offer を破棄する必要があります。

motion イベント
enter で入ったウィンドウ内で、ポインタが移動した時に来ます。
ウィンドウ内に配置した各ウィジェットごとに D&D 判定を行いたい場合は、ここで処理する必要があります (enter 時もですが)。

drop イベント
ユーザーがボタンを離して、ドロップ先がそのドロップを受け付ける場合に来ます。
ドロップ先が D&D を拒否した場合、このイベントは発生せず、キャンセル扱いになります。

ここでは、データ受信の開始や、D&D 処理の終了を通知したりします。
wl_data_offer のイベントハンドラ
wl_data_device#data_offer イベントが来た時、wl_data_offer_add_listener()wl_data_offer のイベントハンドラを設定すると、D&D 元の情報を取得することができます。

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

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

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

offer イベント
クリップボードの時も使いましたが、ソース側が送信可能な MIME タイプが一つずつ通知されます。
wl_data_device#data_offer イベントの直後に来ます。
この情報で、D&D 元データの種類を判別することができます。

source_actions イベント
ソース側が対応可能なアクションが通知されます。
wl_data_device#enter イベントの直前、または、D&D 中に wl_data_source_set_actions() でドロップ先が対応可能なアクションを変更した場合に送信されます。

* ver 3 以降

action
サーバーによって、ドロップ先が最終的に実行するべきアクション (1つまたはなし) が選択&変更された時に来ます。

サーバーは、ソース側が対応可能なアクションと、ドロップ先が受け入れることが出来るアクションの情報を照らし合わせて、ドロップ時に実行するべきアクションを一つ選択します。

なお、ドロップ時に Ctrl や Shift が押されていた場合にアクションを動的に変更する機能がサーバー側で実装されている場合、サーバーが独自の判断でアクションを変更する可能性があります。

ドロップ先では、このイベントで最後に受け取ったアクションを、ドロップ時の最終的なアクションとして扱う必要があります。

* ver 3 以降
動作例
D&D 開始からドロップまでに発生するイベントを見てみます。
なお、D&D 元として送られる情報は省いています。

& pointer.leave
<enter 直前>
# data_device.data_offer: 0x5569eec4e830
+ data_offer.offer: text/plain;charset=utf-8
+ data_offer.source_actions: COPY MOVE ASK
<enter>
# data_device.enter: (137, 218) 0x5569eec4e830
+ data_offer.action: ASK
<ポインタ移動>
# data_device.motion: (136, 217)
# data_device.motion: (136, 217)
...
<対応アクション変更>
+ data_offer.action: COPY
<ドロップ>
# data_device.drop
# data_device.leave
& pointer.enter
@@ recv

まず、D&D が開始され、ポインタがクライアントの管理するウィンドウ内に入った時、wl_data_device#enter イベントが来ますが、その前に、ソース側の情報が送られてきます。

この時点で、D&D 元のデータは "text/plain;charset=utf-8" で、COPY/MOVE/ASK のアクションに対応していることがわかります。

enter 後、wl_data_offer#action によって、現在位置でドロップされた場合に実行するべきアクションが送られます。
ここでは、ASK になっています。

クライアントのウィンドウ内でポインタが移動している間は、wl_data_device#motion イベントが来ます。
受け取る側は、その位置でドロップされた場合に対応可能なアクションや、受け取りたい MIME タイプを通知します。

ドロップ先のウィジェットの種類が変わった時など、受け取る側が途中で対応可能なアクションを変更した場合、再び wl_data_offer#action が送られます。
ここでは、ASK から COPY に変更されました。

ユーザーがボタンを離した時、その位置へのドロップが可能なら、wl_data_device#drop が来ます。
ここで、データ受信の開始などの処理を行います。

最後に、D&D 処理が完全に終了した時は、wl_data_device#leave が来るので、終了処理を行います。
なお、ソース側でデータの送信が行われるのは leave 後のため、注意してください。
イベントの処理
※ GNOME の場合、D&D 処理は、ver 3 以降の方法でしかドロップできないようです。

wl_data_device # enter
wl_data_device#enter イベントが来たら、クライアントはドロップの判定を開始します。
送られてきた wl_data_offer と、serial 値を記録しておきます。

また、enter 時のポインタ位置から、その位置でドロップを受け付けるかどうかを通知します。

ドロップを受け付けるか
ver 3 以降の場合は、enter と motion イベントでドロップ対象位置が変わった時、
wl_data_offer_set_actions()wl_data_offer_accept() を使って、その位置で対応可能なアクションと MIME タイプを通知する必要があります。
ポインタ移動時は毎回この処理をしないと、ドロップが行なえません。

ver 3 未満の場合は、wl_data_offer_accept() だけ使います。

また、今回はマウスカーソルの形状を変更していませんが、本来はドロップ先の状態が変わるごとにカーソル形状を変える処理を行うべきです。

## アクションを指定 (ver 3 以降)

void wl_data_offer_set_actions(struct wl_data_offer *wl_data_offer,
    uint32_t dnd_actions, uint32_t preferred_action);

## 受け入れ可能な MIME タイプを指定

void wl_data_offer_accept(struct wl_data_offer *wl_data_offer, uint32_t serial, const char *mime_type);

wl_data_offer_set_actions
dnd_actions は、対応可能なアクションをフラグで複数指定します。
preferred_action は、dnd_actions で指定した中で、優先するアクションを一つ指定します。

WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE (0) を指定した場合や、ソース側で対応しているアクションと一つも一致しなかった場合、その位置でボタンが離されると、D&D はキャンセル扱いになります。

wl_data_offer_accept
serial は、wl_data_device#enter イベント時に渡された値を指定します。
mime_type で、MIME タイプを指定します。

なお、ここで指定する MIME タイプは、目安みたいなものです。
ver 3 以降の場合は、NULL ならドロップを受け付けない、それ以外ならドロップを受け付ける、という形になります。
GNOME の場合は、適当な文字列を指定しても問題なくドロップできました。
とりあえず、ソース側が対応している MIME タイプのうちいずれか一つを指定しておけば良いでしょう。

実際にデータを受信する際には、そこで受信する MIME タイプを指定します。

wl_data_device # motion
ポインタが移動して、ドロップ対象の位置が変わると、motion イベントが来ます。
enter の時と同じように、アクションと MIME タイプを通知します。

wl_data_offer # action (ver 3 以降)
wl_data_offer_set_actions() で、ドロップ側で対応可能なアクションが変更された時、ソース側の対応可能なアクションと照合して、最終的に実行するべきアクションが変わったら、wl_data_offer#action イベントが来ます。

ここで指定されたアクションは、ドロップ先が最終的に実行するべきアクションなので、実際にドロップされた時のために記録しておきます。

wl_data_device # drop
現在位置がドロップ可能な状態で、ユーザーがボタンを離した時、wl_data_device#drop イベントが来ます。
ドロップ先がドロップを受け付けない状態の時は、来ません。
詳細は、次で説明します。

wl_data_device # leave
D&D 処理が終了した時、もしくは、D&D ポインタがウィンドウ外に出た時に wl_data_device#leave イベントが来ます。
現在位置にドロップが出来ない状態でボタンが離された時は、drop イベントが呼ばれずに、このイベントで終了します。

ここでは、D&D の終了処理を行う必要があります。
wl_data_offer_destroy() で、enter 時に受け取った wl_data_offer を破棄します。
他にもデータを確保したりしていたら、解放します。
ドロップ時の処理
wl_data_device#drop イベントが来た時、ソース側からデータを受信し、ドロップ処理の終了を通知します。

## データの受信を要求

void wl_data_offer_receive(struct wl_data_offer *wl_data_offer, const char *mime_type, int32_t fd);

## ドロップ処理が終わったことを通知

void wl_data_offer_finish(struct wl_data_offer *wl_data_offer);

データの受信方法は、クリップボードの時と同じです。
pipe で fd を作成した後、wl_data_offer_receive() で受け取りたい MIME タイプを指定して、データの受信を要求します。
その後、イベントループ内で、poll などを使って入力を待ち、受信していきます。

ver 3 以降の場合は、drop イベントの最後に wl_data_offer_finish() を実行して、ドロップ先が drop 処理を終了したことを通知します。

注意点
GNOME 上で実行した時、wl_data_offer_receive() の後、すぐに書き込み用 fd をクローズすると、データの受信が行えない (データが送信されず、すぐに閉じられる) 場合がありました。

wl_data_offer_receive() の後に wl_display_flush() を実行して、クライアントのデータをサーバーに送った後、クローズすると、問題なく動作しました。
おそらくタイミング的な問題だと思われます。
解説 (D&D のソース側)
D&D のソース側の処理を説明します。
ソース側は、wl_data_source を使って、ソースデータを管理します。
D&D の開始
wl_data_source の準備
## 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 のハンドラ設定

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

## ソースが対応可能な MIME タイプを指定

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

ソースデータを管理するために、wl_data_source が必要なので、wl_data_device_manager_create_data_source() で作成します。
また、ハンドラも設定しておきます。

次に、ソースが対応可能な MIME タイプをセットします。
複数対応可能なら、複数回呼び出します。

※ クリップボードの時と同じように、Wayland 上で実行した X11 プログラムで、D&D データとして文字列を扱う場合、"UTF8_STRING" などを指定しておかないと、文字列として認識されません。

対応可能なアクションの設定
void wl_data_source_set_actions(struct wl_data_source *wl_data_source, uint32_t dnd_actions);

ver 3 以降では、D&D を開始する前に、wl_data_source_set_actions() で、ソースデータが対応可能なアクションを指定します。
これは、D&D 開始前に一度だけ実行します。

D&D 開始
void wl_data_device_start_drag(struct wl_data_device *wl_data_device,
    struct wl_data_source *source, struct wl_surface *origin,
    struct wl_surface *icon, uint32_t serial);

wl_data_device_start_drag() で、D&D を開始します。
ポインタの button イベントで、左ボタンなどが押された時に実行します。

source は、データソース。NULL にすると、クライアント内部のみの D&D 操作となります。
origin は、ドラッグを開始するサーフェスを指定します。
serial には、button イベント時に渡されたシリアル値を指定します。

icon は、D&D 中のマウスカーソル形状として使うサーフェスを指定します。
NULL にすると、カーソル形状は D&D が終わるまでずっと同じままです。
ここでサーフェスを指定しないと、ドラッグ先においても、カーソル形状の変更が適用されません。
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);

target イベント
ドロップ先で、enter/move イベント時に wl_data_offer_accept() が実行された時に来ます。
ドロップ先で指定された MIME タイプがそのまま渡されます。
NULL の場合は、ドロップ先がドロップを受け付けないという意味です。

send イベント
データの送信を要求された時。

cancelled イベント
D&D がキャンセル扱いになった時に来ます。
この時点で、wl_data_source は有効ではないので、ソースデータを破棄する必要があります。

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

  • ドロップ先が wl_data_offer_accept() で NULL を指定して、ドロップを受け付けなかった。
  • ドロップ先の最終的なアクションが NONE の状態になった。
  • ボタンを離した先が、有効なサーフェスではなかった。
  • サーバー側が、独自に設定したタイムアウトなどで、操作をキャンセルした。

dnd_drop_performed イベント
ユーザーがボタンを離して、ドロップ先がドロップを受け付けた時。
ドロップ先が wl_data_device#drop を受け取った後に来ます。

ドロップ先でドロップが受け付けられなかった場合など、D&D 処理がキャンセルになった時は、来ません。

ここでは、まだ wl_data_source を破棄しないでください。

ver 3 以降

dnd_finished イベント
D&D 処理がすべて終了した時に来ます。
この時点でもう wl_data_source は使われないので、ソースデータなどを解放できます。

実行されたアクションが MOVE の場合、ここでデータを削除できます。

ver 3 以降

action イベント
サーバーが選択したアクション (一つまたはなし) が通知されます。
ここで渡されたアクションが、ドロップ先で最終的に実行するべきアクションとなります。

アクションが ASK だった場合、wl_data_source#dnd_finished イベントの直前にこのイベントが来て、最終的にドロップ先が選択したアクションが通知されます。
そのため、MOVE アクション時のデータ削除などは、wl_data_source#dnd_finished イベント時に行う必要がある。

ver 3 以降
イベント動作
同じウィンドウ内で D&D をする場合の動作は、以下のようになります。

<D&D開始>
& pointer.leave
$ data_source.target: NULL
# data_device.data_offer: 0x563b5676a6c0
+ data_offer.offer: text/plain;charset=utf-8
+ data_offer.offer: UTF8_STRING
$ data_source.action: NONE
+ data_offer.action: NONE
+ data_offer.source_actions: COPY MOVE ASK
# data_device.enter: (109, 191) 0x563b5676a6c0
$ data_source.action: ASK
+ data_offer.action: ASK
$ data_source.target: text/plain;charset=utf-8
# data_device.motion: (110, 191)
$ data_source.target: text/plain;charset=utf-8
# data_device.motion: (110, 189)
...
$ data_source.target: text/plain;charset=utf-8
# data_device.motion: (105, 148)
<アクション変更>
$ data_source.action: COPY
+ data_offer.action: COPY
$ data_source.target: text/plain;charset=utf-8
...
# data_device.motion: (105, 144)
$ data_source.target: text/plain;charset=utf-8
# data_device.motion: (107, 76)
$ data_source.target: text/plain;charset=utf-8
<ドロップ>
# data_device.drop
$ data_source.dnd_drop_performed
# data_device.leave
& pointer.enter
$ data_source.send: text/plain;charset=utf-8
$ data_source.dnd_finished
@@ recv: 8 (8/1024)
@@ recv >
dnd_test

------- キャンセル時

$ data_source.target: NULL
$ data_source.cancelled: 0x563b5676b5b0

ソース側では、D&D 開始時はまだドロップ対象が存在しないので、target と action イベントで NULL と NONE が来ます。
ドロップ先で enter が来たら、アクションと MIME タイプが通知されてきます。
その後は、ドロップ先で motion する度に、target イベントが送られてきます。
ドロップ先でアクションが変わると、action イベントが再度来ます。

ボタンが離され、ドロップが受け付けられると、dnd_drop_performed が来ます。
ドロップ先で操作が終了した後、send イベントが来るので、データを送信する準備をします。
その後、dnd_finished でソース側の処理はすべて終了です。

ボタンを離した時、ドロップ先でドロップが受け付けられない状態の場合、D&D がキャンセルになります。
キャンセルになった時は、dnd_drop_performed と dnd_finished は発生せず、cancelled だけが来て終了するので、そこで終了処理を行います。
データの送信
注意点
データの送信に関しては、注意点があります。
特に、Wayland 上で動かしている X11 アプリに対してドロップした場合、問題が出る場合があります。

クリップボードの時のように、send イベント内で write() を使ってデータを送信すると、一部のアプリでは問題があり、送った側が強制終了してしまいます。
GNOME 上で試した所、leafpad (GTK+2) に対して文字列を D&D すると、send イベント時に強制終了しました。

leafpad に文字列をドロップした時の動作を見てみます。
(以下は、poll で fd が書き込めるまで待っています)

$ data_source.dnd_drop_performed
$ data_source.send: UTF8_STRING, fd:4
$ data_source.send: UTF8_STRING, fd:5
write: fd:4, POLLOUT POLLERR
write: fd:5, POLLOUT
$ data_source.dnd_finished

ドロップ時に、leafpad から2回データの送信を要求されます。
書き込み用の fd は、それぞれで新しい fd が作成されています。
(ドロップ先が複数回データの送信を要求することはあるので、それ自体は問題ではありません)

その後、poll で fd に書き込めるまで待つと、fd 4 の時に POLLERR が発生し、エラーとなっています。
この状態で、fd 4 に対して書き込みを行うと、強制終了します。

POLLERR が発生した場合、その fd に対してはデータを送信せずにクローズし、次の fd でデータを送信すると、ドロップは成功しました。

書き込み時も poll などを使って待つ形にすると、エラー対策も出来るため、正常に動作させることができます。
また、ドロップ先から複数回同時にデータを要求される場合もあるので、その対策も必要です。

今回の場合
今回の場合は、送信/受信ともに poll を使っています。

drop/send イベント時に、poll で待つ fd を登録して、メインループ内で poll を行います。
読み書き出来る状態になったら、登録したハンドラを実行して、処理が終わったら fd を閉じて、poll のリストから削除します。
ASK アクションについて
ASK アクションを使うと、ドロップ時に、ユーザーに対して実行するアクションを選択させることができます。

例えば、ファイルを別の場所に D&D する場合、ASK アクションで、ドロップ時に 「コピー、移動、キャンセル」 などのメニューを表示して、ユーザーに選択させることができます。
※ ソース側とドロップ先の両方が ASK アクションに対応していることが必須となります。

同じクライアント同士で D&D する場合は、コピーや移動以外のコマンドを独自に追加して処理することもできます。

(1) ソース側で ASK に対応する
まずは、ソース側が ASK アクションに対応する必要があります。
D&D 開始時に、wl_data_source_set_actions() で、ASK アクションを指定しておきます。

wl_data_source_set_actions(p->data_source,
    WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY
        | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE
        | WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK);

(2) ドロップ先が ASK に対応する
ドロップ先で ASK を受け付ける場合は、enter/motion イベント時に、wl_data_offer_set_actions() で、ASK に対応していることを通知します。

ドロップ時に常に ASK を実行したい場合は、以下のようにします。

wl_data_offer_set_actions(p->data_offer,
    WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK,
    WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK);

(3) ドロップ先で drop イベントが来た時
ドロップ先の wl_data_offer#action イベントで、最後に来た値が ASK だった場合、ドロップ先は ASK アクションの処理を行う必要があります。

drop イベントが来たら、メニューなどを表示して、ユーザーにアクションを選択させます。

その後、wl_data_offer_set_actions() を使って、選択したアクションをソース側に通知します。
そして、最後に wl_data_offer_finish() を呼び出して、D&D 処理の終了を通知します。

wl_data_offer_set_actions(p->data_offer,
    WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY,
    WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);

wl_data_offer_finish(p->data_offer);

ユーザーがキャンセルを選んだ場合は、上記の処理を行わずに、wl_data_offer_destroy()wl_data_offer を破棄します。
すると、ソース側では wl_data_source#cancelled が発生します。

(4) ソース側の最終処理
wl_data_source#dnd_drop_performed イベントの後、ドロップ先で最終的なアクションが決定されたら、
wl_data_source#actionwl_data_source#dnd_finished イベントが来ます。

最終的なアクションが MOVE だった場合は、dnd_finished イベント時に、移動元データの削除を行います。