+static void data_offer_unref(struct data_offer *offer)
+{
+ unsigned int i, len;
+
+ if (!offer || !offer->ref || --offer->ref)
+ return;
+
+ len = shl_array_get_length(offer->types);
+ for (i = 0; i < len; ++i)
+ free(*SHL_ARRAY_AT(offer->types, char*, i));
+ shl_array_free(offer->types);
+ wl_data_offer_destroy(offer->w_offer);
+ free(offer);
+}
+
+static void data_offer_offer(void *data, struct wl_data_offer *w_offer,
+ const char *type)
+{
+ char *tmp;
+ int ret;
+ struct data_offer *offer = wl_data_offer_get_user_data(w_offer);
+
+ tmp = strdup(type);
+ if (!tmp)
+ return;
+
+ ret = shl_array_push(offer->types, &tmp);
+ if (ret) {
+ free(tmp);
+ return;
+ }
+}
+
+static const struct wl_data_offer_listener data_offer_listener = {
+ .offer = data_offer_offer,
+};
+
+static void data_dev_data_offer(void *data, struct wl_data_device *w_dev,
+ struct wl_data_offer *w_offer)
+{
+ struct data_offer *offer;
+ struct wlt_display *disp = data;
+ int ret;
+
+ offer = malloc(sizeof(*offer));
+ if (!offer) {
+ wl_data_offer_destroy(w_offer);
+ return;
+ }
+ memset(offer, 0, sizeof(*offer));
+ offer->ref = 1;
+ offer->w_offer = w_offer;
+ offer->disp = disp;
+
+ ret = shl_array_new(&offer->types, sizeof(char*), 4);
+ if (ret) {
+ wl_data_offer_destroy(w_offer);
+ free(offer);
+ return;
+ }
+
+ wl_data_offer_add_listener(w_offer, &data_offer_listener, offer);
+ wl_data_offer_set_user_data(w_offer, offer);
+}
+
+static void data_dev_enter(void *data, struct wl_data_device *w_dev,
+ uint32_t serial, struct wl_surface *w_surface,
+ wl_fixed_t x, wl_fixed_t y,
+ struct wl_data_offer *w_offer)
+{
+ struct wlt_display *disp = data;
+
+ if (disp->drag_offer) {
+ data_offer_unref(disp->drag_offer);
+ disp->drag_offer = NULL;
+ }
+
+ if (!w_offer)
+ return;
+
+ disp->drag_offer = wl_data_offer_get_user_data(w_offer);
+}
+
+static void data_dev_leave(void *data, struct wl_data_device *w_dev)
+{
+ struct wlt_display *disp = data;
+
+ if (disp->drag_offer) {
+ data_offer_unref(disp->drag_offer);
+ disp->drag_offer = NULL;
+ }
+}
+
+static void data_dev_motion(void *data, struct wl_data_device *w_dev,
+ uint32_t time, wl_fixed_t x, wl_fixed_t y)
+{
+}
+
+static void data_dev_drop(void *data, struct wl_data_device *w_dev)
+{
+}
+
+static void data_dev_selection(void *data, struct wl_data_device *w_dev,
+ struct wl_data_offer *w_offer)
+{
+ struct wlt_display *disp = data;
+
+ if (disp->selection_offer) {
+ data_offer_unref(disp->selection_offer);
+ disp->selection_offer = NULL;
+ }
+
+ if (!w_offer)
+ return;
+
+ disp->selection_offer = wl_data_offer_get_user_data(w_offer);
+}
+
+int wlt_display_get_selection_to_fd(struct wlt_display *disp, const char *mime,
+ int output_fd)
+{
+ unsigned int i, num;
+ struct data_offer *offer;
+
+ if (!disp || !mime)
+ return -EINVAL;
+ if (!disp->selection_offer)
+ return -ENOENT;
+
+ offer = disp->selection_offer;
+ num = shl_array_get_length(offer->types);
+ for (i = 0; i < num; ++i) {
+ if (!strcmp(mime, *SHL_ARRAY_AT(offer->types, char*, i)))
+ break;
+ }
+
+ if (i == num)
+ return -EAGAIN;
+
+ wl_data_offer_receive(offer->w_offer, mime, output_fd);
+ return 0;
+}
+
+int wlt_display_get_selection_fd(struct wlt_display *disp, const char *mime)
+{
+ int p[2], ret;
+
+ if (pipe2(p, O_CLOEXEC | O_NONBLOCK))
+ return -EFAULT;
+
+ ret = wlt_display_get_selection_to_fd(disp, mime, p[1]);
+ close(p[1]);
+
+ if (ret) {
+ close(p[0]);
+ return ret;
+ }
+
+ return p[0];
+}
+
+int wlt_display_new_data_source(struct wlt_display *disp,
+ struct wl_data_source **out)
+{
+ struct wl_data_source *src;
+
+ if (!disp)
+ return -EINVAL;
+
+ src = wl_data_device_manager_create_data_source(disp->w_manager);
+ if (!src)
+ return -EFAULT;
+
+ *out = src;
+ return 0;
+}
+
+void wlt_display_set_selection(struct wlt_display *disp,
+ struct wl_data_source *selection)
+{
+ if (!disp)
+ return;
+
+ wl_data_device_set_selection(disp->w_data_dev, selection,
+ disp->last_serial);
+}
+
+static const struct wl_data_device_listener data_dev_listener = {
+ .data_offer = data_dev_data_offer,
+ .enter = data_dev_enter,
+ .leave = data_dev_leave,
+ .motion = data_dev_motion,
+ .drop = data_dev_drop,
+ .selection = data_dev_selection,
+};
+