diff --git a/WINGs/wview_wl.c b/WINGs/wview_wl.c index ceb8121..c15adfa 100644 --- a/WINGs/wview_wl.c +++ b/WINGs/wview_wl.c @@ -16,6 +16,10 @@ static unsigned int wl_view_next_id = WL_VIEW_ID_BASE; void (*W_ViewCommitBackingFn)(unsigned long id) = NULL; void (*W_ViewDestroyBackingFn)(unsigned long id) = NULL; +/* Clipboard hooks set by WM backend for cross-client clipboard */ +void (*W_ClipboardStoreFn)(const char *text, int len) = NULL; +char *(*W_ClipboardFetchFn)(int *len_out) = NULL; + extern void wl_view_destroy_backing_impl(unsigned long id); /* Simple view lookup table (ID → W_View*) */ @@ -492,6 +496,8 @@ static void wl_clipboard_store(void *vctx, const char *text, int len) memcpy(wl_clipboard_buf, text, len); wl_clipboard_buf[len] = '\0'; wl_clipboard_len = len; + if (W_ClipboardStoreFn) + W_ClipboardStoreFn(text, len); } else { wl_clipboard_buf = NULL; wl_clipboard_len = 0; @@ -501,6 +507,17 @@ static void wl_clipboard_store(void *vctx, const char *text, int len) static char *wl_clipboard_fetch(void *vctx, int *len_out) { (void)vctx; + /* Try seat selection first (cross-client paste) */ + if (W_ClipboardFetchFn) { + int n = 0; + char *text = W_ClipboardFetchFn(&n); + if (text) { + if (len_out) + *len_out = n; + return text; + } + } + /* Fall back to internal buffer */ if (wl_clipboard_buf && wl_clipboard_len > 0) { char *copy = wmalloc(wl_clipboard_len + 1); memcpy(copy, wl_clipboard_buf, wl_clipboard_len); diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 341eb12..13591bf 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -667,6 +667,9 @@ static void handle_seat_request_cursor(struct wl_listener *listener, void *data) static void handle_seat_request_set_selection(struct wl_listener *listener, void *data); /* Phase 26: primary selection request handler. */ static void handle_seat_request_primary_selection(struct wl_listener *listener, void *data); +/* Compositor-internal clipboard hooks for WINGs. */ +static void wl_clipboard_store_to_seat(const char *text, int len); +static char *wl_clipboard_fetch_from_seat(int *len_out); /* Phase 28: idle inhibitor created/destroyed handler. */ static void handle_idle_new_inhibitor(struct wl_listener *listener, void *data); /* Phase 29: scroll-wheel axis event handler. */ @@ -2323,6 +2326,11 @@ wl_display_open(const char *name) W_ViewCommitBackingFn = wl_view_commit_backing_impl; extern void (*W_ViewDestroyBackingFn)(unsigned long id); W_ViewDestroyBackingFn = wl_view_destroy_backing_impl; + + extern void (*W_ClipboardStoreFn)(const char *text, int len); + extern char *(*W_ClipboardFetchFn)(int *len_out); + W_ClipboardStoreFn = wl_clipboard_store_to_seat; + W_ClipboardFetchFn = wl_clipboard_fetch_from_seat; } if (wl_state.scene) { wwarning("wl_backend: scene graph created"); @@ -4710,6 +4718,103 @@ handle_seat_request_cursor(struct wl_listener *listener, void *data) ev->hotspot_x, ev->hotspot_y); } +/* ------------------------------------------------------------------ */ +/* Compositor-internal clipboard (WINGs ↔ Wayland clients) */ +/* ------------------------------------------------------------------ */ + +static char *wl_seat_clipboard_text = NULL; +static int wl_seat_clipboard_len = 0; +static struct wlr_data_source wl_seat_clipboard_source; + +static void wl_clipboard_source_send(struct wlr_data_source *source, + const char *mime_type, int32_t fd) +{ + (void)source; (void)mime_type; + if (wl_seat_clipboard_text && wl_seat_clipboard_len > 0) + write(fd, wl_seat_clipboard_text, wl_seat_clipboard_len); + close(fd); +} + +static void wl_clipboard_source_destroy(struct wlr_data_source *source) +{ + (void)source; + /* Don't free — static lifetime, text freed separately */ +} + +static const struct wlr_data_source_impl wl_clipboard_source_impl = { + .send = wl_clipboard_source_send, + .destroy = wl_clipboard_source_destroy, +}; + +static void wl_clipboard_store_to_seat(const char *text, int len) +{ + if (!wl_state.seat || !text || len <= 0) + return; + + if (wl_seat_clipboard_text) + free(wl_seat_clipboard_text); + wl_seat_clipboard_text = malloc(len + 1); + memcpy(wl_seat_clipboard_text, text, len); + wl_seat_clipboard_text[len] = '\0'; + wl_seat_clipboard_len = len; + + wlr_data_source_init(&wl_seat_clipboard_source, &wl_clipboard_source_impl); + wl_array_release(&wl_seat_clipboard_source.mime_types); + wl_array_init(&wl_seat_clipboard_source.mime_types); + char **mime = wl_array_add(&wl_seat_clipboard_source.mime_types, sizeof(char *)); + *mime = strdup("text/plain;charset=utf-8"); + mime = wl_array_add(&wl_seat_clipboard_source.mime_types, sizeof(char *)); + *mime = strdup("text/plain"); + + wlr_seat_set_selection(wl_state.seat, &wl_seat_clipboard_source, + wl_display_next_serial(wl_state.display)); +} + +static char *wl_clipboard_fetch_from_seat(int *len_out) +{ + if (!wl_state.seat || !wl_state.seat->selection_source) + return NULL; + + /* If we own the selection, return our buffer directly */ + if (wl_state.seat->selection_source == &wl_seat_clipboard_source) { + if (wl_seat_clipboard_text && wl_seat_clipboard_len > 0) { + char *copy = malloc(wl_seat_clipboard_len + 1); + memcpy(copy, wl_seat_clipboard_text, wl_seat_clipboard_len); + copy[wl_seat_clipboard_len] = '\0'; + if (len_out) + *len_out = wl_seat_clipboard_len; + return copy; + } + return NULL; + } + + /* Foreign source — read via pipe */ + int fds[2]; + if (pipe(fds) < 0) + return NULL; + + wlr_data_source_send(wl_state.seat->selection_source, + "text/plain;charset=utf-8", fds[1]); + close(fds[1]); + + char buf[4096]; + int total = 0; + ssize_t n; + while ((n = read(fds[0], buf + total, sizeof(buf) - total - 1)) > 0) + total += n; + close(fds[0]); + + if (total <= 0) + return NULL; + + buf[total] = '\0'; + char *result = malloc(total + 1); + memcpy(result, buf, total + 1); + if (len_out) + *len_out = total; + return result; +} + /* * Phase 25: seat request_set_selection — client calls wl_data_device.set_selection * (i.e. Ctrl-C or other clipboard write). Forward to wlr_seat so the