summaryrefslogtreecommitdiff
path: root/src/wayland/clipboard.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-11 21:53:42 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-11 21:53:42 +0300
commit927ae621c9a3d46dbc401b8e9ea7bbed1bf2e97b (patch)
treeaeb00c16770dcf5a36d28c1167cc39c6759059fb /src/wayland/clipboard.c
initial: Yet Another Surface Library (YASL) 0.1.0
A minimal, modern desktop window surface library in C. Supports Wayland on Linux and Cocoa/Metal on macOS with presentation timing, fractional scaling, IME, and CSD.
Diffstat (limited to 'src/wayland/clipboard.c')
-rw-r--r--src/wayland/clipboard.c621
1 files changed, 621 insertions, 0 deletions
diff --git a/src/wayland/clipboard.c b/src/wayland/clipboard.c
new file mode 100644
index 0000000..324644f
--- /dev/null
+++ b/src/wayland/clipboard.c
@@ -0,0 +1,621 @@
+#define _GNU_SOURCE
+#include "internal.h"
+#include "clipboard.h"
+
+#define CLIPBOARD_TEXT_MAX (256u * 1024)
+
+struct offer {
+ struct offer *next;
+ struct wl_data_offer *proxy;
+ char *mime;
+ bool uri_list;
+ bool tab;
+};
+struct primary_offer {
+ struct primary_offer *next;
+ struct zwp_primary_selection_offer_v1 *proxy;
+ char *mime;
+};
+struct transfer {
+ struct transfer *next;
+ struct clipboard *owner;
+ struct offer *drop;
+ struct wl_surface *drop_surface;
+ int fd, id;
+ char *text;
+ size_t len, offset, capacity;
+ bool sending;
+ bool uri_list;
+};
+struct clipboard {
+ struct wl_display *display;
+ struct wl_data_device_manager *manager;
+ struct wl_data_device *device;
+ struct zwp_primary_selection_device_manager_v1 *primary_manager;
+ struct zwp_primary_selection_device_v1 *primary_device;
+ struct zwp_primary_selection_source_v1 *primary_source;
+ struct wl_seat *seat;
+ struct wl_data_source *source;
+ struct offer *selection;
+ struct offer *pending_offers;
+ struct offer *drag_offer;
+ struct primary_offer *primary_selection;
+ struct primary_offer *primary_pending_offers;
+ struct yasl_loop *loop;
+ struct transfer *transfers;
+ char *text;
+ char *primary_text;
+ clipboard_receive_fn receive;
+ void *ud;
+ clipboard_drag_fn drag;
+ void *drag_ud;
+ clipboard_drop_fn drop;
+ void *drop_ud;
+ wl_fixed_t drag_x, drag_y;
+ struct wl_surface *drag_surface;
+};
+
+const char *clipboard_drag_mime(void)
+{
+ static char mime[64];
+ if (!mime[0])
+ snprintf(mime, sizeof(mime), "application/x-yasl-tab;pid=%ld", (long)getpid());
+ return mime;
+}
+
+static void drag_event(struct clipboard *c, enum yasl_drag_event ev,
+ wl_fixed_t x, wl_fixed_t y)
+{
+ if (c->drag)
+ c->drag(ev, c->drag_surface, wl_fixed_to_double(x), wl_fixed_to_double(y),
+ c->drag_ud);
+}
+
+static bool offer_droppable(const struct offer *o)
+{
+ return o && !o->tab && (o->uri_list || o->mime);
+}
+
+static void drop_event(struct clipboard *c, enum yasl_drop_event ev)
+{
+ if (c->drop && c->drag_surface)
+ c->drop(ev, c->drag_surface, wl_fixed_to_double(c->drag_x),
+ wl_fixed_to_double(c->drag_y), c->drop_ud);
+}
+
+static void free_offer(struct offer *o);
+
+static void transfer_end(struct transfer *t, bool complete)
+{
+ struct transfer **p = &t->owner->transfers;
+ while (*p && *p != t) p = &(*p)->next;
+ if (*p) *p = t->next;
+ yasl_loop_del_fd(t->owner->loop, t->id);
+ close(t->fd);
+ if (t->drop) {
+ if (complete && wl_data_offer_get_version(t->drop->proxy) >=
+ WL_DATA_OFFER_FINISH_SINCE_VERSION)
+ wl_data_offer_finish(t->drop->proxy);
+ free_offer(t->drop);
+ wl_display_flush(t->owner->display);
+ }
+ free(t->text);
+ free(t);
+}
+
+static void transfer_destroy(struct transfer *t)
+{
+ transfer_end(t, false);
+}
+
+static int transfer_io(int fd, short events, void *ud)
+{
+ struct transfer *t = ud;
+ if (events & (POLLERR | POLLNVAL)) { transfer_destroy(t); return 0; }
+ if (t->sending) {
+ ssize_t n = write(fd, t->text + t->offset, t->len - t->offset);
+ if (n > 0) t->offset += (size_t)n;
+ if (t->offset == t->len || n == 0 ||
+ (n < 0 && errno != EAGAIN && errno != EINTR)) transfer_destroy(t);
+ } else {
+ char buf[8192];
+ ssize_t n = read(fd, buf, sizeof(buf));
+ if (n > 0) {
+ size_t need = t->len + (size_t)n;
+ if (need > CLIPBOARD_TEXT_MAX) { transfer_destroy(t); return 0; }
+ if (need > t->capacity) {
+ size_t capacity = t->capacity ? t->capacity * 2 : sizeof(buf);
+ if (capacity > CLIPBOARD_TEXT_MAX) capacity = CLIPBOARD_TEXT_MAX;
+ char *text = realloc(t->text, capacity);
+ if (!text) { transfer_destroy(t); return 0; }
+ t->text = text;
+ t->capacity = capacity;
+ }
+ memcpy(t->text + t->len, buf, (size_t)n);
+ t->len += n;
+ } else if (n == 0) {
+ if (t->len) {
+ t->owner->receive(t->text, t->len, t->uri_list,
+ t->drop_surface, t->owner->ud);
+ }
+ transfer_end(t, true);
+ } else if (errno != EAGAIN && errno != EINTR) transfer_destroy(t);
+ }
+ return 0;
+}
+
+static struct transfer *start_transfer_text(struct clipboard *c, int fd, bool sending,
+ bool uri_list, const char *send_text)
+{
+ struct transfer *t = calloc(1, sizeof(*t));
+ if (!t) { close(fd); return NULL; }
+ t->owner = c; t->fd = fd; t->sending = sending; t->uri_list = uri_list;
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+ if (sending) {
+ t->text = strdup(send_text ? send_text : "");
+ if (!t->text) { close(fd); free(t); return NULL; }
+ t->len = strlen(t->text);
+ }
+ t->id = yasl_loop_add_fd(c->loop, fd, sending ? POLLOUT : POLLIN, transfer_io, t);
+ if (t->id < 0) { close(fd); free(t->text); free(t); return NULL; }
+ t->next = c->transfers; c->transfers = t;
+ return t;
+}
+
+static struct transfer *start_transfer(struct clipboard *c, int fd, bool sending, bool uri_list)
+{
+ return start_transfer_text(c, fd, sending, uri_list, c->text);
+}
+
+static const char *match_text_mime(const char *mime)
+{
+ if (!strcasecmp(mime, "text/plain;charset=utf-8")) return "text/plain;charset=utf-8";
+ if (!strcasecmp(mime, "text/plain;charset=utf8")) return "text/plain;charset=utf8";
+ if (!strcasecmp(mime, "text/plain")) return "text/plain";
+ if (!strcasecmp(mime, "UTF8_STRING")) return "UTF8_STRING";
+ if (!strcasecmp(mime, "STRING")) return "STRING";
+ if (!strcasecmp(mime, "TEXT")) return "TEXT";
+ return NULL;
+}
+
+static void offer_keep_mime(char **slot, const char *mime)
+{
+ char *copy = strdup(mime);
+ if (!copy) return;
+ free(*slot);
+ *slot = copy;
+}
+
+static void offer_mime(void *ud, struct wl_data_offer *proxy, const char *mime)
+{
+ (void)proxy;
+ struct offer *o = ud;
+ if (!strcmp(mime, clipboard_drag_mime())) o->tab = true;
+ if (!strcmp(mime, "text/uri-list")) o->uri_list = true;
+ const char *match = match_text_mime(mime);
+ if (match) {
+ if (!o->mime || !strcasecmp(match, "text/plain;charset=utf-8"))
+ offer_keep_mime(&o->mime, mime);
+ }
+}
+static void offer_source_actions(void *ud, struct wl_data_offer *proxy, uint32_t actions)
+{ (void)ud; (void)proxy; (void)actions; }
+static void offer_action(void *ud, struct wl_data_offer *proxy, uint32_t action)
+{ (void)ud; (void)proxy; (void)action; }
+static const struct wl_data_offer_listener offer_listener = {
+ .offer = offer_mime,
+ .source_actions = offer_source_actions,
+ .action = offer_action,
+};
+static void free_offer(struct offer *o)
+{
+ if (o) { wl_data_offer_destroy(o->proxy); free(o->mime); free(o); }
+}
+static void take_pending_offer(struct clipboard *c, struct offer *o)
+{
+ if (!o) return;
+ struct offer **p = &c->pending_offers;
+ while (*p && *p != o) p = &(*p)->next;
+ if (*p) *p = o->next;
+ o->next = NULL;
+}
+static void clear_pending_offers(struct clipboard *c)
+{
+ while (c->pending_offers) {
+ struct offer *o = c->pending_offers;
+ c->pending_offers = o->next;
+ free_offer(o);
+ }
+}
+static void data_offer(void *ud, struct wl_data_device *device, struct wl_data_offer *proxy)
+{
+ (void)device;
+ struct clipboard *c = ud;
+ struct offer *o = calloc(1, sizeof(*o));
+ if (!o) { wl_data_offer_destroy(proxy); return; }
+ o->proxy = proxy;
+ wl_data_offer_add_listener(proxy, &offer_listener, o);
+ o->next = c->pending_offers;
+ c->pending_offers = o;
+}
+static void selection(void *ud, struct wl_data_device *device, struct wl_data_offer *proxy)
+{
+ (void)device;
+ struct clipboard *c = ud;
+ struct offer *o = proxy ? wl_data_offer_get_user_data(proxy) : NULL;
+ take_pending_offer(c, o);
+ if (c->drag_offer == o) c->drag_offer = NULL;
+ if (c->selection != o) free_offer(c->selection);
+ c->selection = o;
+}
+static void drag_offer_enter(void *ud, struct wl_data_device *device, uint32_t serial,
+ struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *proxy)
+{
+ (void)device;
+ if (proxy) {
+ struct clipboard *c = ud;
+ struct offer *o = wl_data_offer_get_user_data(proxy);
+ take_pending_offer(c, o);
+ if (c->selection == o) c->selection = NULL;
+ if (c->drag_offer && c->drag_offer != o) free_offer(c->drag_offer);
+ c->drag_offer = o;
+ const char *mime = o && o->tab ? clipboard_drag_mime()
+ : o && o->uri_list ? "text/uri-list"
+ : o ? o->mime : NULL;
+ wl_data_offer_accept(proxy, serial, mime);
+ if (mime && wl_data_offer_get_version(proxy) >= 3) {
+ uint32_t action = o->tab ? WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE
+ : WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+ wl_data_offer_set_actions(proxy, action, action);
+ }
+ c->drag_x = x;
+ c->drag_y = y;
+ c->drag_surface = surface;
+ if (o && o->tab)
+ drag_event(c, YASL_DRAG_OVER, x, y);
+ else if (offer_droppable(o))
+ drop_event(c, YASL_DROP_OVER);
+ }
+}
+static void drag_offer_clear(void *ud, struct wl_data_device *device)
+{
+ (void)device;
+ struct clipboard *c = ud;
+ if (c->drag_offer && c->drag_offer->tab)
+ drag_event(c, YASL_DRAG_LEAVE, c->drag_x, c->drag_y);
+ else if (offer_droppable(c->drag_offer))
+ drop_event(c, YASL_DROP_LEAVE);
+ free_offer(c->drag_offer);
+ c->drag_offer = NULL;
+ c->drag_surface = NULL;
+}
+static void drag_motion(void *ud, struct wl_data_device *device, uint32_t time, wl_fixed_t x, wl_fixed_t y)
+{
+ (void)device; (void)time;
+ struct clipboard *c = ud;
+ c->drag_x = x;
+ c->drag_y = y;
+ if (c->drag_offer && c->drag_offer->tab)
+ drag_event(c, YASL_DRAG_OVER, x, y);
+ else if (offer_droppable(c->drag_offer))
+ drop_event(c, YASL_DROP_OVER);
+}
+static void drag_drop(void *ud, struct wl_data_device *device)
+{
+ (void)device;
+ struct clipboard *c = ud;
+ struct offer *o = c->drag_offer;
+ if (!o) return;
+ c->drag_offer = NULL;
+ bool v3 = wl_data_offer_get_version(o->proxy) >= 3;
+ if (o->tab) {
+ drag_event(c, YASL_DRAG_DROP, c->drag_x, c->drag_y);
+ if (v3)
+ wl_data_offer_finish(o->proxy);
+ } else if (offer_droppable(o)) {
+ drop_event(c, YASL_DROP_LEAVE);
+ int fds[2];
+ if (pipe2(fds, O_CLOEXEC) == 0) {
+ wl_data_offer_receive(o->proxy, o->uri_list ? "text/uri-list" : o->mime, fds[1]);
+ close(fds[1]);
+ struct transfer *t = start_transfer(c, fds[0], false, o->uri_list);
+ if (t) {
+ t->drop = o;
+ t->drop_surface = c->drag_surface;
+ o = NULL;
+ }
+ wl_display_flush(c->display);
+ }
+ }
+ c->drag_surface = NULL;
+ free_offer(o);
+}
+static const struct wl_data_device_listener device_listener = {
+ .data_offer = data_offer, .selection = selection, .enter = drag_offer_enter,
+ .leave = drag_offer_clear, .motion = drag_motion, .drop = drag_drop
+};
+static void source_target(void *ud, struct wl_data_source *s, const char *mime) { (void)ud; (void)s; (void)mime; }
+static void source_send(void *ud, struct wl_data_source *s, const char *mime, int32_t fd)
+{ (void)s; (void)mime; start_transfer(ud, fd, true, false); }
+static void source_cancelled(void *ud, struct wl_data_source *s)
+{
+ struct clipboard *c = ud;
+ if (c->source == s) c->source = NULL;
+ wl_data_source_destroy(s);
+}
+static void source_dnd_drop_performed(void *ud, struct wl_data_source *s) { (void)ud; (void)s; }
+static void source_dnd_finished(void *ud, struct wl_data_source *s) { (void)ud; (void)s; }
+static void source_action(void *ud, struct wl_data_source *s, uint32_t action) { (void)ud; (void)s; (void)action; }
+static const struct wl_data_source_listener source_listener = {
+ .target = source_target, .send = source_send, .cancelled = source_cancelled,
+ .dnd_drop_performed = source_dnd_drop_performed,
+ .dnd_finished = source_dnd_finished, .action = source_action,
+};
+
+static void free_primary_offer(struct primary_offer *o)
+{
+ if (o) { zwp_primary_selection_offer_v1_destroy(o->proxy); free(o->mime); free(o); }
+}
+
+static void take_pending_primary_offer(struct clipboard *c, struct primary_offer *o)
+{
+ if (!o) return;
+ struct primary_offer **p = &c->primary_pending_offers;
+ while (*p && *p != o) p = &(*p)->next;
+ if (*p) *p = o->next;
+ o->next = NULL;
+}
+
+static void clear_pending_primary_offers(struct clipboard *c)
+{
+ while (c->primary_pending_offers) {
+ struct primary_offer *o = c->primary_pending_offers;
+ c->primary_pending_offers = o->next;
+ free_primary_offer(o);
+ }
+}
+
+static void primary_offer_mime(void *ud, struct zwp_primary_selection_offer_v1 *proxy, const char *mime)
+{
+ (void)proxy;
+ struct primary_offer *o = ud;
+ const char *match = match_text_mime(mime);
+ if (match) {
+ if (!o->mime || !strcasecmp(match, "text/plain;charset=utf-8"))
+ offer_keep_mime(&o->mime, mime);
+ }
+}
+
+static const struct zwp_primary_selection_offer_v1_listener primary_offer_listener = {
+ .offer = primary_offer_mime,
+};
+
+static void primary_data_offer(void *ud, struct zwp_primary_selection_device_v1 *device,
+ struct zwp_primary_selection_offer_v1 *proxy)
+{
+ (void)device;
+ struct clipboard *c = ud;
+ struct primary_offer *o = calloc(1, sizeof(*o));
+ if (!o) { zwp_primary_selection_offer_v1_destroy(proxy); return; }
+ o->proxy = proxy;
+ zwp_primary_selection_offer_v1_add_listener(proxy, &primary_offer_listener, o);
+ o->next = c->primary_pending_offers;
+ c->primary_pending_offers = o;
+}
+
+static void primary_selection(void *ud, struct zwp_primary_selection_device_v1 *device,
+ struct zwp_primary_selection_offer_v1 *proxy)
+{
+ (void)device;
+ struct clipboard *c = ud;
+ struct primary_offer *o = proxy ? zwp_primary_selection_offer_v1_get_user_data(proxy) : NULL;
+ take_pending_primary_offer(c, o);
+ if (c->primary_selection != o) free_primary_offer(c->primary_selection);
+ c->primary_selection = o;
+}
+
+static const struct zwp_primary_selection_device_v1_listener primary_device_listener = {
+ .data_offer = primary_data_offer,
+ .selection = primary_selection,
+};
+
+static void primary_source_send(void *ud, struct zwp_primary_selection_source_v1 *s,
+ const char *mime, int32_t fd)
+{
+ (void)s; (void)mime;
+ struct clipboard *c = ud;
+ start_transfer_text(c, fd, true, false, c->primary_text);
+}
+
+static void primary_source_cancelled(void *ud, struct zwp_primary_selection_source_v1 *s)
+{
+ struct clipboard *c = ud;
+ if (c->primary_source == s) c->primary_source = NULL;
+ zwp_primary_selection_source_v1_destroy(s);
+}
+
+static const struct zwp_primary_selection_source_v1_listener primary_source_listener = {
+ .send = primary_source_send,
+ .cancelled = primary_source_cancelled,
+};
+
+void clipboard_set_primary_manager(struct clipboard *c,
+ struct zwp_primary_selection_device_manager_v1 *mgr)
+{
+ if (!c) return;
+ c->primary_manager = mgr;
+ if (c->primary_device) {
+ zwp_primary_selection_device_v1_destroy(c->primary_device);
+ c->primary_device = NULL;
+ }
+ if (c->primary_manager && c->seat) {
+ c->primary_device =
+ zwp_primary_selection_device_manager_v1_get_device(c->primary_manager, c->seat);
+ if (c->primary_device)
+ zwp_primary_selection_device_v1_add_listener(c->primary_device,
+ &primary_device_listener, c);
+ }
+}
+
+void clipboard_set_drag_handler(struct clipboard *c, clipboard_drag_fn fn, void *ud)
+{
+ if (!c) return;
+ c->drag = fn;
+ c->drag_ud = ud;
+}
+
+void clipboard_set_drop_handler(struct clipboard *c, clipboard_drop_fn fn, void *ud)
+{
+ if (!c) return;
+ c->drop = fn;
+ c->drop_ud = ud;
+}
+
+struct wl_data_device *clipboard_data_device(const struct clipboard *c)
+{
+ return c ? c->device : NULL;
+}
+
+struct clipboard *clipboard_create(struct wl_display *display, struct wl_data_device_manager *manager,
+ struct wl_seat *seat, struct yasl_loop *loop,
+ clipboard_receive_fn receive, void *ud)
+{
+ if (!manager || !seat) return NULL;
+ struct clipboard *c = calloc(1, sizeof(*c));
+ if (!c) return NULL;
+ c->display = display; c->manager = manager; c->loop = loop; c->receive = receive; c->ud = ud;
+ c->device = wl_data_device_manager_get_data_device(manager, seat);
+ if (!c->device) { free(c); return NULL; }
+ c->seat = seat;
+ wl_data_device_add_listener(c->device, &device_listener, c);
+ return c;
+}
+
+bool clipboard_set_seat(struct clipboard *c, struct wl_seat *seat)
+{
+ if (!c || c->seat == seat) return false;
+ c->seat = seat;
+ drag_offer_clear(c, c->device);
+ if (c->device) {
+ wl_data_device_destroy(c->device);
+ c->device = NULL;
+ }
+ free_offer(c->selection);
+ c->selection = NULL;
+ clear_pending_offers(c);
+ if (c->source) {
+ wl_data_source_destroy(c->source);
+ c->source = NULL;
+ }
+ if (c->primary_device) {
+ zwp_primary_selection_device_v1_destroy(c->primary_device);
+ c->primary_device = NULL;
+ }
+ free_primary_offer(c->primary_selection);
+ c->primary_selection = NULL;
+ clear_pending_primary_offers(c);
+ if (c->primary_source) {
+ zwp_primary_selection_source_v1_destroy(c->primary_source);
+ c->primary_source = NULL;
+ }
+ if (!seat) return true;
+ if (c->manager) {
+ c->device = wl_data_device_manager_get_data_device(c->manager, seat);
+ if (c->device)
+ wl_data_device_add_listener(c->device, &device_listener, c);
+ }
+ if (c->primary_manager) {
+ c->primary_device =
+ zwp_primary_selection_device_manager_v1_get_device(c->primary_manager, seat);
+ if (c->primary_device)
+ zwp_primary_selection_device_v1_add_listener(c->primary_device,
+ &primary_device_listener, c);
+ }
+ return true;
+}
+
+void clipboard_copy(struct clipboard *c, const char *text, uint32_t serial)
+{
+ if (!c || !c->device || !text || !serial) return;
+ char *copy = strdup(text);
+ if (!copy) return;
+ if (c->source) wl_data_source_destroy(c->source);
+ free(c->text); c->text = copy;
+ c->source = wl_data_device_manager_create_data_source(c->manager);
+ if (!c->source) return;
+ wl_data_source_add_listener(c->source, &source_listener, c);
+ wl_data_source_offer(c->source, "text/plain;charset=utf-8");
+ wl_data_source_offer(c->source, "text/plain");
+ wl_data_source_offer(c->source, "UTF8_STRING");
+ wl_data_source_offer(c->source, "STRING");
+ wl_data_source_offer(c->source, "TEXT");
+ wl_data_device_set_selection(c->device, c->source, serial);
+}
+
+void clipboard_paste(struct clipboard *c)
+{
+ if (!c || !c->device || !c->selection || !c->selection->mime) return;
+ int fds[2];
+ if (pipe2(fds, O_CLOEXEC) < 0) return;
+ wl_data_offer_receive(c->selection->proxy, c->selection->mime, fds[1]);
+ close(fds[1]);
+ start_transfer(c, fds[0], false, false);
+ wl_display_flush(c->display);
+}
+
+void clipboard_copy_primary(struct clipboard *c, const char *text, uint32_t serial)
+{
+ if (!c || !text || !serial) return;
+ if (!c->primary_device || !c->primary_manager) {
+ clipboard_copy(c, text, serial);
+ return;
+ }
+ char *copy = strdup(text);
+ if (!copy) return;
+ if (c->primary_source) zwp_primary_selection_source_v1_destroy(c->primary_source);
+ free(c->primary_text); c->primary_text = copy;
+ c->primary_source = zwp_primary_selection_device_manager_v1_create_source(c->primary_manager);
+ if (!c->primary_source) return;
+ zwp_primary_selection_source_v1_add_listener(c->primary_source, &primary_source_listener, c);
+ zwp_primary_selection_source_v1_offer(c->primary_source, "text/plain;charset=utf-8");
+ zwp_primary_selection_source_v1_offer(c->primary_source, "text/plain");
+ zwp_primary_selection_source_v1_offer(c->primary_source, "UTF8_STRING");
+ zwp_primary_selection_source_v1_offer(c->primary_source, "STRING");
+ zwp_primary_selection_source_v1_offer(c->primary_source, "TEXT");
+ zwp_primary_selection_device_v1_set_selection(c->primary_device, c->primary_source, serial);
+}
+
+void clipboard_paste_primary(struct clipboard *c)
+{
+ if (!c) return;
+ if (c->primary_device && c->primary_selection && c->primary_selection->mime) {
+ int fds[2];
+ if (pipe2(fds, O_CLOEXEC) < 0) return;
+ zwp_primary_selection_offer_v1_receive(c->primary_selection->proxy,
+ c->primary_selection->mime, fds[1]);
+ close(fds[1]);
+ start_transfer(c, fds[0], false, false);
+ wl_display_flush(c->display);
+ return;
+ }
+ clipboard_paste(c);
+}
+
+void clipboard_destroy(struct clipboard *c)
+{
+ if (!c) return;
+ while (c->transfers)
+ transfer_destroy(c->transfers);
+ free_offer(c->selection);
+ clear_pending_offers(c);
+ free_offer(c->drag_offer);
+ free_primary_offer(c->primary_selection);
+ clear_pending_primary_offers(c);
+ if (c->primary_source) zwp_primary_selection_source_v1_destroy(c->primary_source);
+ if (c->primary_device) zwp_primary_selection_device_v1_destroy(c->primary_device);
+ free(c->primary_text);
+ if (c->source) wl_data_source_destroy(c->source);
+ if (c->device) wl_data_device_destroy(c->device);
+ free(c->text); free(c);
+}