WINGs: cross-client clipboard via wlr_seat selection

Bridges WINGs internal clipboard to the Wayland seat selection so
copy/paste works between compositor-internal dialogs and real
Wayland clients. Store creates a wlr_data_source and sets seat
selection. Fetch reads from foreign selection sources via pipe.
This commit is contained in:
Window Maker 2026-05-23 14:59:42 +02:00
parent 0dc7e33e4b
commit cee271f2cd
2 changed files with 122 additions and 0 deletions

View File

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

View File

@ -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