From 6745b7bd4927bc647131fafc5bd4e9071f831604 Mon Sep 17 00:00:00 2001 From: JiDe Zhang Date: Fri, 19 Sep 2025 15:58:58 +0800 Subject: [PATCH] selection: allows using a wrapper source to filter data 1. Added support for intercepting clipboard selection operations. 2. Wraps data sources to control data provision based on client PID. 3. Added wlr_data_receiver to data source's send to identify the recipient. 4. Includes new example "clipboard-control.c" to illustrate clipboard permission management. 5. Added PID and client members to data source and receiver for access control. --- examples/clipboard-control.c | 1996 +++++++++++++++++++++ examples/meson.build | 5 + include/wlr/types/wlr_data_device.h | 49 +- include/wlr/types/wlr_data_receiver.h | 95 + include/wlr/types/wlr_primary_selection.h | 31 +- include/xwayland/selection.h | 11 +- types/data_device/wlr_data_device.c | 1 + types/data_device/wlr_data_offer.c | 25 +- types/data_device/wlr_data_source.c | 97 +- types/meson.build | 1 + types/wlr_data_control_v1.c | 51 +- types/wlr_data_receiver.c | 55 + types/wlr_ext_data_control_v1.c | 43 +- types/wlr_primary_selection.c | 56 +- types/wlr_primary_selection_v1.c | 69 +- xwayland/selection/incoming.c | 82 +- xwayland/selection/outgoing.c | 96 +- xwayland/selection/selection.c | 68 +- 18 files changed, 2723 insertions(+), 108 deletions(-) create mode 100644 examples/clipboard-control.c create mode 100644 include/wlr/types/wlr_data_receiver.h create mode 100644 types/wlr_data_receiver.c diff --git a/examples/clipboard-control.c b/examples/clipboard-control.c new file mode 100644 index 000000000..4236e1878 --- /dev/null +++ b/examples/clipboard-control.c @@ -0,0 +1,1996 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xwayland/selection.h" +#include +#include +#include +#include +#include +#include +#include + +/* Clipboard control data source wrapper */ +struct clipboard_data_source { + struct wlr_data_source base; + struct wlr_data_source *wrapped_source; + struct wlr_seat *seat; + struct clipboard_server *server; + + struct wl_listener wrapped_source_destroy; + struct wl_list cache_link; // For wrapper cache reuse +}; + +/* Primary selection control data source wrapper */ +struct clipboard_primary_source { + struct wlr_primary_selection_source base; + struct wlr_primary_selection_source *wrapped_source; + struct wlr_seat *seat; + struct clipboard_server *server; + + struct wl_listener wrapped_source_destroy; + struct wl_list cache_link; // For wrapper cache reuse +}; + +enum clipboard_request_type { + CLIPBOARD_REQUEST_SELECTION, + CLIPBOARD_REQUEST_PRIMARY, + CLIPBOARD_REQUEST_DRAG, +}; + +/* Pending clipboard request */ +struct clipboard_request { + enum clipboard_request_type type; + union { + struct clipboard_data_source *data_source; + struct clipboard_primary_source *primary_source; + }; + struct wlr_data_receiver *receiver; + char *mime_type; + + /* For async processing */ + struct clipboard_server *server; + bool waiting_for_response; + + struct wl_list link; + + /* Listener for receiver destroy signal */ + struct wl_listener receiver_destroy; +}; + +enum clipboard_cursor_mode { + CLIPBOARD_CURSOR_PASSTHROUGH, + CLIPBOARD_CURSOR_MOVE, + CLIPBOARD_CURSOR_RESIZE, +}; + +struct clipboard_server { + struct wl_display *wl_display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_scene *scene; + struct wlr_scene_output_layout *scene_layout; + struct wlr_compositor *compositor; + + struct wlr_xdg_shell *xdg_shell; + struct wl_listener new_xdg_toplevel; + struct wl_listener new_xdg_popup; + struct wl_list toplevels; + + struct wlr_cursor *cursor; + struct wlr_xcursor_manager *cursor_mgr; + struct wl_listener cursor_motion; + struct wl_listener cursor_motion_absolute; + struct wl_listener cursor_button; + struct wl_listener cursor_axis; + struct wl_listener cursor_frame; + + struct wlr_seat *seat; + struct wl_listener new_input; + struct wl_listener request_cursor; + struct wl_listener pointer_focus_change; + struct wl_listener request_set_selection; + struct wl_list keyboards; + enum clipboard_cursor_mode cursor_mode; + struct clipboard_toplevel *grabbed_toplevel; + double grab_x, grab_y; + struct wlr_box grab_geobox; + uint32_t resize_edges; + + struct wlr_output_layout *output_layout; + struct wl_list outputs; + struct wl_listener new_output; + + struct wlr_xwayland *xwayland; + + /* XWayland surface support */ + struct wl_listener xwayland_new_surface; + + /* Startup command storage */ + char *startup_cmd; + struct wl_listener xwayland_ready; + + /* Primary selection support */ + struct wlr_primary_selection_v1_device_manager *primary_selection_manager; + struct wl_listener request_set_primary_selection; + + /* Drag and drop support */ + struct wl_listener request_start_drag; + struct wl_listener start_drag; + + /* Clipboard control */ + struct wl_list pending_requests; // clipboard_request::link + + /* Wrapper caches for reuse */ + struct wl_list active_data_source_wrappers; // clipboard_data_source::cache_link + struct wl_list active_primary_source_wrappers; // clipboard_primary_source::cache_link + + /* Dialog system */ + bool dialog_visible; + struct wlr_scene_buffer *dialog_buffer; + struct clipboard_request *current_request; + struct wlr_buffer *dialog_wlr_buffer; +}; + +/* PID query function for X11 windows */ +/* Get command line for a PID (Linux/proc filesystem only) */ +static char* get_pid_cmdline(pid_t pid) { + if (pid <= 0) { + return NULL; + } + +#ifdef __linux__ + // Check if /proc filesystem is available + struct stat st; + if (stat("/proc", &st) != 0 || !S_ISDIR(st.st_mode)) { + return NULL; + } + + char proc_path[256]; + snprintf(proc_path, sizeof(proc_path), "/proc/%d/cmdline", pid); + + FILE *file = fopen(proc_path, "r"); + if (!file) { + return NULL; + } + + char *cmdline = calloc(1024, 1); + if (!cmdline) { + fclose(file); + return NULL; + } + + size_t len = fread(cmdline, 1, 1023, file); + fclose(file); + + if (len == 0) { + free(cmdline); + return NULL; + } + + // Replace null bytes with spaces (cmdline uses null separators) + for (size_t i = 0; i < len; i++) { + if (cmdline[i] == '\0') { + cmdline[i] = ' '; + } + } + + // Remove trailing spaces + while (len > 0 && cmdline[len - 1] == ' ') { + cmdline[--len] = '\0'; + } + + return cmdline; +#else + // Not supported on non-Linux systems + (void)pid; + return NULL; +#endif +} + +/* Cairo buffer implementation for dialog */ +struct cairo_dialog_buffer { + struct wlr_buffer base; + cairo_surface_t *surface; +}; + +static void cairo_dialog_buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct cairo_dialog_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + wlr_buffer_finish(wlr_buffer); + cairo_surface_destroy(buffer->surface); + free(buffer); +} + +static bool cairo_dialog_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, + uint32_t flags, void **data, uint32_t *format, size_t *stride) { + struct cairo_dialog_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + + if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) { + return false; + } + + *format = DRM_FORMAT_ARGB8888; + *data = cairo_image_surface_get_data(buffer->surface); + *stride = cairo_image_surface_get_stride(buffer->surface); + return true; +} + +static void cairo_dialog_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { + // Nothing to do +} + +static const struct wlr_buffer_impl cairo_dialog_buffer_impl = { + .destroy = cairo_dialog_buffer_destroy, + .begin_data_ptr_access = cairo_dialog_buffer_begin_data_ptr_access, + .end_data_ptr_access = cairo_dialog_buffer_end_data_ptr_access +}; + +/* Handle receiver destroy signal */ +static void handle_request_receiver_destroy(struct wl_listener *listener, void *data) { + struct clipboard_request *request = + wl_container_of(listener, request, receiver_destroy); + + printf("Receiver destroyed, cleaning up request\n"); + + /* Remove the receiver reference */ + request->receiver = NULL; + + wl_list_remove(&request->receiver_destroy.link); +} + +static void safe_receiver_cancelled(struct wlr_data_receiver *receiver) { + if (receiver) { + wlr_data_receiver_cancelled(receiver); + } +} + +/* Create dialog buffer using Cairo */ +static struct wlr_buffer *create_dialog_buffer(struct clipboard_server *server, + struct clipboard_request *request) { + const int width = 480; + const int height = 200; + + struct cairo_dialog_buffer *buffer = calloc(1, sizeof(*buffer)); + if (!buffer) { + return NULL; + } + + wlr_buffer_init(&buffer->base, &cairo_dialog_buffer_impl, width, height); + + buffer->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + if (cairo_surface_status(buffer->surface) != CAIRO_STATUS_SUCCESS) { + free(buffer); + return NULL; + } + + // Draw the dialog using Cairo + cairo_t *cr = cairo_create(buffer->surface); + + // Clear background with blue dialog color + cairo_set_source_rgba(cr, 0.2, 0.3, 0.5, 0.9); + cairo_paint(cr); + + // Draw border + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); + cairo_set_line_width(cr, 4.0); + cairo_rectangle(cr, 2, 2, width - 4, height - 4); + cairo_stroke(cr); + + // Draw title + cairo_set_source_rgba(cr, 1.0, 1.0, 0.0, 1.0); + cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size(cr, 16); + cairo_move_to(cr, 10, 25); + + const char *operation_type = ""; + switch (request->type) { + case CLIPBOARD_REQUEST_SELECTION: + operation_type = "Clipboard Selection Request"; + break; + case CLIPBOARD_REQUEST_PRIMARY: + operation_type = "Primary Selection Request"; + break; + case CLIPBOARD_REQUEST_DRAG: + operation_type = "Drag & Drop Request"; + break; + } + cairo_show_text(cr, operation_type); + +#ifdef __linux__ + // Check if /proc filesystem is available + struct stat st; + bool proc_available = (stat("/proc", &st) == 0 && S_ISDIR(st.st_mode)); +#else + bool proc_available = false; +#endif + + // Draw content with conditional command line info + cairo_set_font_size(cr, 12); + int current_y = 50; + + char info_text[512]; + + // Get source PID + pid_t source_pid = 0; + if (request->type == CLIPBOARD_REQUEST_SELECTION && + request->data_source && request->data_source->wrapped_source) { + source_pid = request->data_source->wrapped_source->pid; + } else if (request->type == CLIPBOARD_REQUEST_PRIMARY && + request->primary_source && request->primary_source->wrapped_source) { + source_pid = request->primary_source->wrapped_source->pid; + } + + // Source PID info + cairo_move_to(cr, 10, current_y); + if (proc_available) { + char *source_cmdline = get_pid_cmdline(source_pid); + if (source_cmdline) { + snprintf(info_text, sizeof(info_text), "Source: PID %d (%s)", + source_pid, source_cmdline); + free(source_cmdline); + } else { + snprintf(info_text, sizeof(info_text), "Source: PID %d", source_pid); + } + } else { + snprintf(info_text, sizeof(info_text), "Source: PID %d", source_pid); + } + cairo_show_text(cr, info_text); + current_y += 20; + + // Target info + cairo_move_to(cr, 10, current_y); + pid_t target_pid = request->receiver ? request->receiver->pid : 0; + if (proc_available && target_pid > 0) { + char *target_cmdline = get_pid_cmdline(target_pid); + if (target_cmdline) { + snprintf(info_text, sizeof(info_text), "Target: PID %d (%s)", + target_pid, target_cmdline); + free(target_cmdline); + } else { + snprintf(info_text, sizeof(info_text), "Target: PID %d", target_pid); + } + } else if (target_pid > 0) { + snprintf(info_text, sizeof(info_text), "Target: PID %d", target_pid); + } else { + snprintf(info_text, sizeof(info_text), "Target: Unknown client"); + } + cairo_show_text(cr, info_text); + current_y += 20; + + // MIME type + cairo_move_to(cr, 10, current_y); + snprintf(info_text, sizeof(info_text), "MIME type: %s", request->mime_type); + cairo_show_text(cr, info_text); + + // Draw instructions + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); + cairo_set_font_size(cr, 14); + cairo_move_to(cr, 10, 140); + cairo_show_text(cr, "Press Y to Allow, N to Deny"); + + // Draw keyboard hint boxes + cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 0.8); + cairo_rectangle(cr, 10, 160, 30, 25); + cairo_fill(cr); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_move_to(cr, 20, 178); + cairo_show_text(cr, "Y"); + + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.8); + cairo_rectangle(cr, 50, 160, 30, 25); + cairo_fill(cr); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_move_to(cr, 60, 178); + cairo_show_text(cr, "N"); + + cairo_destroy(cr); + + return &buffer->base; +} + +/* Show graphical dialog for clipboard approval */ +static void show_graphical_dialog(struct clipboard_server *server, + struct clipboard_request *request) { + if (server->dialog_visible) { + // Hide existing dialog first + if (server->dialog_buffer) { + wlr_scene_node_destroy(&server->dialog_buffer->node); + server->dialog_buffer = NULL; + } + if (server->dialog_wlr_buffer) { + wlr_buffer_drop(server->dialog_wlr_buffer); + server->dialog_wlr_buffer = NULL; + } + } + + // Create dialog buffer + server->dialog_wlr_buffer = create_dialog_buffer(server, request); + if (!server->dialog_wlr_buffer) { + printf("Failed to create dialog buffer, falling back to console\n"); + return; + } + + // Add dialog to scene at the center + server->dialog_buffer = wlr_scene_buffer_create(&server->scene->tree, server->dialog_wlr_buffer); + if (!server->dialog_buffer) { + wlr_buffer_drop(server->dialog_wlr_buffer); + server->dialog_wlr_buffer = NULL; + return; + } + + // Center the dialog on screen + struct wlr_output *output = wlr_output_layout_get_center_output(server->output_layout); + if (output) { + wlr_scene_node_set_position(&server->dialog_buffer->node, + output->width / 2 - 240, output->height / 2 - 100); + } else { + // Default position if no output found + wlr_scene_node_set_position(&server->dialog_buffer->node, 100, 100); + } + + server->dialog_visible = true; + server->current_request = request; + + printf("Graphical dialog shown - Press Y to allow, N to deny\n"); +} + +/* Process next pending dialog request */ +static void process_next_pending_dialog(struct clipboard_server *server) { + if (server->dialog_visible) { + // Already showing a dialog + return; + } + + if (wl_list_empty(&server->pending_requests)) { + // No pending requests + return; + } + + // Get the next request from the pending list + struct clipboard_request *next_request = wl_container_of( + server->pending_requests.next, next_request, link); + + // Remove from pending list (but don't free it yet) + wl_list_remove(&next_request->link); + + // Show the dialog for this request + if (server->scene) { + show_graphical_dialog(server, next_request); + } + + printf("Processing next pending dialog request\n"); +} + +/* Hide the graphical dialog */ +static void hide_dialog(struct clipboard_server *server) { + if (!server->dialog_visible) { + return; + } + + if (server->dialog_buffer) { + wlr_scene_node_destroy(&server->dialog_buffer->node); + server->dialog_buffer = NULL; + } + + if (server->dialog_wlr_buffer) { + wlr_buffer_drop(server->dialog_wlr_buffer); + server->dialog_wlr_buffer = NULL; + } + + server->dialog_visible = false; + server->current_request = NULL; + + // Process next pending dialog request + process_next_pending_dialog(server); +} + +/* Handle dialog response */ +static void handle_dialog_response(struct clipboard_server *server, bool approved) { + if (!server->dialog_visible || !server->current_request) { + return; + } + + struct clipboard_request *request = server->current_request; + + if (approved) { + printf("✓ Clipboard transfer approved via GUI\n\n"); + } else { + printf("✗ Clipboard transfer denied via GUI\n\n"); + } + + // Process the request based on its type + switch (request->type) { + case CLIPBOARD_REQUEST_SELECTION: + if (approved && request->data_source && + request->data_source->wrapped_source && request->receiver) { + // Send data using the new interface + wlr_data_source_send(request->data_source->wrapped_source, + request->mime_type, request->receiver); + } else { + safe_receiver_cancelled(request->receiver); + } + break; + + case CLIPBOARD_REQUEST_PRIMARY: + if (approved && request->primary_source && + request->primary_source->wrapped_source && request->receiver) { + // Send data using the new interface + wlr_primary_selection_source_send(request->primary_source->wrapped_source, + request->mime_type, request->receiver); + } else { + // Handle denial + safe_receiver_cancelled(request->receiver); + } + break; + + case CLIPBOARD_REQUEST_DRAG: + // Drag operations are handled differently + break; + } + + /* Remove receiver destroy listener if receiver still exists */ + if (request->receiver) { + wl_list_remove(&request->receiver_destroy.link); + } + + free(request->mime_type); + free(request); + + hide_dialog(server); +} + +/* Show graphical dialog for clipboard approval - ASYNC */ +static void show_clipboard_dialog(struct clipboard_server *server, + struct clipboard_request *request) { + // Store the request for async processing + request->server = server; + request->waiting_for_response = true; + + // If no dialog is currently visible, show this one immediately + if (!server->dialog_visible) { + // Show graphical dialog (if possible) + if (server->scene) { + show_graphical_dialog(server, request); + } else { + printf("Failed to show graphical dialog, using console fallback\n"); + } + } else { + // Add to pending requests list + wl_list_insert(&server->pending_requests, &request->link); + printf("Dialog already visible, added request to pending queue\n"); + } + + // Print console info as well + const char *operation_type = ""; + switch (request->type) { + case CLIPBOARD_REQUEST_SELECTION: + operation_type = "Clipboard Selection"; + break; + case CLIPBOARD_REQUEST_PRIMARY: + operation_type = "Primary Selection"; + break; + case CLIPBOARD_REQUEST_DRAG: + operation_type = "Drag & Drop"; + break; + } + + printf("\n========== %s Request ==========\n", operation_type); + + // Show source process information with command line + pid_t source_pid = 0; + if (request->type == CLIPBOARD_REQUEST_SELECTION && + request->data_source && request->data_source->wrapped_source) { + source_pid = request->data_source->wrapped_source->pid; + } else if (request->type == CLIPBOARD_REQUEST_PRIMARY && + request->primary_source && request->primary_source->wrapped_source) { + source_pid = request->primary_source->wrapped_source->pid; + } + + printf("Source PID: %d", source_pid); + char *source_cmdline = get_pid_cmdline(source_pid); + if (source_cmdline) { + printf(" (%s)", source_cmdline); + free(source_cmdline); + } + printf("\n"); + + // Show target process information + pid_t target_pid = request->receiver ? request->receiver->pid : 0; + if (target_pid > 0) { + printf("Target PID: %d", target_pid); + char *target_cmdline = get_pid_cmdline(target_pid); + if (target_cmdline) { + printf(" (%s)", target_cmdline); + free(target_cmdline); + } + printf("\n"); + } else { + printf("Target: Unknown client\n"); + } + printf("MIME type: %s\n", request->mime_type); + printf("================================================\n"); + printf("Press Y to Allow, N to Deny\n"); +} + +static void clipboard_data_source_send(struct wlr_data_source *source, + const char *mime_type, struct wlr_data_receiver *receiver) { + struct clipboard_data_source *wrapper = + wl_container_of(source, wrapper, base); + + if (!wrapper->wrapped_source) { + wlr_data_receiver_cancelled(receiver); + return; + } + + /* Create clipboard request */ + struct clipboard_request *request = calloc(1, sizeof(*request)); + if (!request) { + wlr_data_receiver_cancelled(receiver); + return; + } + + request->type = CLIPBOARD_REQUEST_SELECTION; + request->data_source = wrapper; + request->receiver = receiver; + request->mime_type = strdup(mime_type); + + /* Set up receiver destroy listener */ + request->receiver_destroy.notify = handle_request_receiver_destroy; + wl_signal_add(&receiver->events.destroy, &request->receiver_destroy); + + /* Show dialog for approval - ASYNC */ + show_clipboard_dialog(wrapper->server, request); + + /* The actual transfer will happen in handle_dialog_response */ + /* Don't cleanup request here - it will be cleaned up after user response */ +} + +static void clipboard_data_source_accept(struct wlr_data_source *source, + uint32_t serial, const char *mime_type, struct wlr_data_receiver *receiver) { + struct clipboard_data_source *wrapper = + wl_container_of(source, wrapper, base); + + if (!wrapper->wrapped_source) { + return; + } + + wlr_data_source_accept(wrapper->wrapped_source, serial, mime_type, receiver); +} + +static void remove_data_source_wrapper(struct clipboard_data_source *wrapper) { + printf("Removing data source wrapper from active list\n"); + wl_list_remove(&wrapper->cache_link); + free(wrapper); +} + +static void clipboard_data_source_destroy(struct wlr_data_source *source) { + struct clipboard_data_source *wrapper = + wl_container_of(source, wrapper, base); + + if (wrapper->wrapped_source) { + wl_list_remove(&wrapper->wrapped_source_destroy.link); + wlr_data_source_destroy(wrapper->wrapped_source); + } + + /* Return wrapper to cache instead of freeing */ + remove_data_source_wrapper(wrapper); +} + +static struct wlr_data_source *clipboard_data_source_get_original(struct wlr_data_source *source) { + struct clipboard_data_source *wrapper = + wl_container_of(source, wrapper, base); + + if (wrapper->wrapped_source) { + return wlr_data_source_get_original(wrapper->wrapped_source); + } + + return source; +} + +static void handle_wrapped_source_destroy(struct wl_listener *listener, void *data) { + struct clipboard_data_source *wrapper = + wl_container_of(listener, wrapper, wrapped_source_destroy); + + wl_list_remove(&wrapper->wrapped_source_destroy.link); + wrapper->wrapped_source = NULL; +} + +static const struct wlr_data_source_impl clipboard_source_impl = { + .send = clipboard_data_source_send, + .accept = clipboard_data_source_accept, + .destroy = clipboard_data_source_destroy, + .get_original = clipboard_data_source_get_original, +}; + +/* Primary selection wrapper functions */ +static void clipboard_primary_source_send(struct wlr_primary_selection_source *source, + const char *mime_type, struct wlr_data_receiver *receiver) { + struct clipboard_primary_source *wrapper = + wl_container_of(source, wrapper, base); + + if (!wrapper->wrapped_source) { + wlr_data_receiver_cancelled(receiver); + return; + } + + /* Create primary selection request */ + struct clipboard_request *request = calloc(1, sizeof(*request)); + if (!request) { + wlr_data_receiver_cancelled(receiver); + wlr_primary_selection_source_send(wrapper->wrapped_source, mime_type, receiver); + return; + } + + request->type = CLIPBOARD_REQUEST_PRIMARY; + request->primary_source = wrapper; + request->receiver = receiver; + request->mime_type = strdup(mime_type); + + /* Set up receiver destroy listener */ + request->receiver_destroy.notify = handle_request_receiver_destroy; + wl_signal_add(&receiver->events.destroy, &request->receiver_destroy); + + /* Show dialog for approval - ASYNC */ + show_clipboard_dialog(wrapper->server, request); + + /* The actual transfer will happen in handle_dialog_response */ + /* Don't cleanup request here - it will be cleaned up after user response */ +} + +static void remove_primary_source_wrapper(struct clipboard_primary_source *wrapper) { + printf("Removing primary source wrapper from active list\n"); + wl_list_remove(&wrapper->cache_link); + free(wrapper); +} + +static void clipboard_primary_source_destroy(struct wlr_primary_selection_source *source) { + struct clipboard_primary_source *wrapper = + wl_container_of(source, wrapper, base); + + if (wrapper->wrapped_source) { + wl_list_remove(&wrapper->wrapped_source_destroy.link); + wlr_primary_selection_source_destroy(wrapper->wrapped_source); + } + + /* Return wrapper to cache instead of freeing */ + remove_primary_source_wrapper(wrapper); +} + +static struct wlr_primary_selection_source *clipboard_primary_source_get_original(struct wlr_primary_selection_source *source) { + struct clipboard_primary_source *wrapper = + wl_container_of(source, wrapper, base); + + if (wrapper->wrapped_source) { + return wlr_primary_selection_source_get_original(wrapper->wrapped_source); + } + + return source; +} + +static void handle_wrapped_primary_source_destroy(struct wl_listener *listener, void *data) { + struct clipboard_primary_source *wrapper = + wl_container_of(listener, wrapper, wrapped_source_destroy); + + wl_list_remove(&wrapper->wrapped_source_destroy.link); + wrapper->wrapped_source = NULL; +} + +static const struct wlr_primary_selection_source_impl clipboard_primary_source_impl = { + .send = clipboard_primary_source_send, + .destroy = clipboard_primary_source_destroy, + .get_original = clipboard_primary_source_get_original, +}; + +/* Wrapper cache management functions */ +static struct clipboard_data_source *get_or_create_data_source_wrapper( + struct clipboard_server *server, struct wlr_data_source *source) { + /* First, try to find existing wrapper for this source */ + struct clipboard_data_source *wrapper; + wl_list_for_each(wrapper, &server->active_data_source_wrappers, cache_link) { + if (wrapper->wrapped_source == source) { + printf("Found existing data source wrapper for source %p\n", source); + /* Update wrapper data */ + return wrapper; + } + } + + /* Allocate new wrapper if none found */ + wrapper = calloc(1, sizeof(struct clipboard_data_source)); + if (!wrapper) { + return NULL; + } + + printf("Creating new data source wrapper for source %p\n", source); + wrapper->wrapped_source = source; + wrapper->seat = server->seat; + wrapper->server = server; + + /* Initialize wrapper with clipboard control impl */ + wlr_data_source_init(&wrapper->base, &clipboard_source_impl); + + /* Add to active list */ + wl_list_insert(&server->active_data_source_wrappers, &wrapper->cache_link); + + /* Listen for original source destruction */ + wrapper->wrapped_source_destroy.notify = handle_wrapped_source_destroy; + wl_signal_add(&source->events.destroy, &wrapper->wrapped_source_destroy); + + return wrapper; +} + +static struct clipboard_primary_source *get_or_create_primary_source_wrapper( + struct clipboard_server *server, struct wlr_primary_selection_source *source) { + /* First, try to find existing wrapper for this source */ + struct clipboard_primary_source *wrapper; + wl_list_for_each(wrapper, &server->active_primary_source_wrappers, cache_link) { + if (wrapper->wrapped_source == source) { + printf("Found existing primary source wrapper for source %p\n", source); + /* Update wrapper data */ + return wrapper; + } + } + + /* Allocate new wrapper if none found */ + wrapper = calloc(1, sizeof(struct clipboard_primary_source)); + if (!wrapper) { + return NULL; + } + + printf("Creating new primary source wrapper for source %p\n", source); + wrapper->wrapped_source = source; + wrapper->seat = server->seat; + wrapper->server = server; + + /* Initialize wrapper with primary selection control impl */ + wlr_primary_selection_source_init(&wrapper->base, &clipboard_primary_source_impl); + + /* Add to active list */ + wl_list_insert(&server->active_primary_source_wrappers, &wrapper->cache_link); + + /* Listen for original source destruction */ + wrapper->wrapped_source_destroy.notify = handle_wrapped_primary_source_destroy; + wl_signal_add(&source->events.destroy, &wrapper->wrapped_source_destroy); + + return wrapper; +} + +static void seat_request_set_selection(struct wl_listener *listener, void *data) { + struct clipboard_server *server = wl_container_of( + listener, server, request_set_selection); + struct wlr_seat_request_set_selection_event *event = data; + + if (event->source == NULL) { + wlr_seat_set_selection(server->seat, NULL, event->serial); + return; + } + + /* Get wrapper from cache or allocate new one */ + struct clipboard_data_source *wrapper = get_or_create_data_source_wrapper(server, event->source); + if (!wrapper) { + return; + } + + /* Copy metadata from original source */ + wlr_data_source_copy(&wrapper->base, event->source); + + /* Use wrapper instead of original source */ + wlr_seat_set_selection(server->seat, &wrapper->base, event->serial); +} + +static void seat_request_set_primary_selection(struct wl_listener *listener, void *data) { + struct clipboard_server *server = wl_container_of( + listener, server, request_set_primary_selection); + struct wlr_seat_request_set_primary_selection_event *event = data; + + if (event->source == NULL) { + wlr_seat_set_primary_selection(server->seat, NULL, event->serial); + return; + } + + /* Get primary selection wrapper from cache or allocate new one */ + struct clipboard_primary_source *wrapper = + get_or_create_primary_source_wrapper(server, event->source); + if (!wrapper) { + return; + } + + /* Copy metadata from original source */ + wlr_primary_selection_source_copy(&wrapper->base, event->source); + + /* Use wrapper instead of original source */ + wlr_seat_set_primary_selection(server->seat, &wrapper->base, event->serial); +} + +static void seat_request_start_drag(struct wl_listener *listener, void *data) { + struct clipboard_server *server = wl_container_of( + listener, server, request_start_drag); + struct wlr_seat_request_start_drag_event *event = data; + + /* DND operations are allowed without permission control */ + printf("✓ Drag & Drop operation allowed (no permission control)\n"); + wlr_seat_start_pointer_drag(server->seat, event->drag, event->serial); +} + +static void seat_start_drag(struct wl_listener *listener, void *data) { + (void)listener; /* Unused parameter */ + (void)data; /* Unused parameter */ + + printf("Drag operation started\n"); +} + +/* XWayland surface data structure */ +struct clipboard_xwayland_surface { + struct wlr_xwayland_surface *xsurface; + struct clipboard_server *server; + struct wlr_scene_surface *scene_surface; + + struct wl_listener associate; + struct wl_listener dissociate; + struct wl_listener destroy; + struct wl_listener set_geometry; +}; + +/* XWayland surface handlers */ +static void xwayland_surface_set_geometry(struct wl_listener *listener, void *data) { + struct clipboard_xwayland_surface *surface = + wl_container_of(listener, surface, set_geometry); + struct wlr_xwayland_surface *xsurface = surface->xsurface; + + printf("XWayland surface geometry changed: %s at position (%d, %d) size %dx%d\n", + xsurface->title ? xsurface->title : "unknown", + xsurface->x, xsurface->y, xsurface->width, xsurface->height); + + // Update the position when geometry changes + if (surface->scene_surface) { + wlr_scene_node_set_position(&surface->scene_surface->buffer->node, + xsurface->x, xsurface->y); + printf("Updated XWayland surface position to (%d, %d)\n", xsurface->x, xsurface->y); + } +} + +static void xwayland_surface_associate(struct wl_listener *listener, void *data) { + struct clipboard_xwayland_surface *surface = + wl_container_of(listener, surface, associate); + struct wlr_xwayland_surface *xsurface = surface->xsurface; + struct clipboard_server *server = surface->server; + + printf("XWayland surface associated: %s (class: %s, PID: %d) at position (%d, %d)\n", + xsurface->title ? xsurface->title : "unknown", + xsurface->class ? xsurface->class : "unknown", + xsurface->pid, xsurface->x, xsurface->y); + + // Now we have the wl_surface, create scene node + if (xsurface->surface) { + surface->scene_surface = wlr_scene_surface_create(&server->scene->tree, xsurface->surface); + if (surface->scene_surface) { + surface->scene_surface->buffer->node.data = surface; + printf("Created scene surface for XWayland surface\n"); + + // Set initial position based on XWayland coordinates + wlr_scene_node_set_position(&surface->scene_surface->buffer->node, + xsurface->x, xsurface->y); + printf("Set initial XWayland surface position to (%d, %d)\n", xsurface->x, xsurface->y); + + // Set keyboard focus for XWayland surface + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(server->seat); + if (keyboard != NULL) { + wlr_seat_keyboard_notify_enter(server->seat, xsurface->surface, + keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); + printf("Set keyboard focus for XWayland surface\n"); + } + } + } +} + +static void xwayland_surface_dissociate(struct wl_listener *listener, void *data) { + struct clipboard_xwayland_surface *surface = + wl_container_of(listener, surface, dissociate); + + printf("XWayland surface dissociated\n"); + + // Clean up scene surface + if (surface->scene_surface) { + wlr_scene_node_destroy(&surface->scene_surface->buffer->node); + surface->scene_surface = NULL; + } +} + +static void xwayland_surface_destroy(struct wl_listener *listener, void *data) { + struct clipboard_xwayland_surface *surface = + wl_container_of(listener, surface, destroy); + + printf("XWayland surface destroyed\n"); + + // Clean up scene surface if still exists + if (surface->scene_surface) { + wlr_scene_node_destroy(&surface->scene_surface->buffer->node); + surface->scene_surface = NULL; + } + + // Remove listeners + wl_list_remove(&surface->associate.link); + wl_list_remove(&surface->dissociate.link); + wl_list_remove(&surface->destroy.link); + wl_list_remove(&surface->set_geometry.link); + + free(surface); +} + +static void server_new_xwayland_surface(struct wl_listener *listener, void *data) { + struct clipboard_server *server = wl_container_of(listener, server, xwayland_new_surface); + struct wlr_xwayland_surface *xsurface = data; + + printf("New XWayland surface: %s (class: %s, PID: %d)\n", + xsurface->title ? xsurface->title : "unknown", + xsurface->class ? xsurface->class : "unknown", + xsurface->pid); + + // Create wrapper structure for the XWayland surface + struct clipboard_xwayland_surface *surface = calloc(1, sizeof(*surface)); + if (!surface) { + return; + } + + surface->xsurface = xsurface; + surface->server = server; + surface->scene_surface = NULL; + + // Set up listeners for associate/dissociate signals + surface->associate.notify = xwayland_surface_associate; + wl_signal_add(&xsurface->events.associate, &surface->associate); + + surface->dissociate.notify = xwayland_surface_dissociate; + wl_signal_add(&xsurface->events.dissociate, &surface->dissociate); + + surface->destroy.notify = xwayland_surface_destroy; + wl_signal_add(&xsurface->events.destroy, &surface->destroy); + + // Set up listener for geometry changes + surface->set_geometry.notify = xwayland_surface_set_geometry; + wl_signal_add(&xsurface->events.set_geometry, &surface->set_geometry); + + // Store our wrapper in the xsurface data + xsurface->data = surface; + + // If already associated (unlikely but possible), handle it immediately + if (xsurface->surface) { + xwayland_surface_associate(&surface->associate, NULL); + } +} + +static void xwayland_ready(struct wl_listener *listener, void *data) { + struct clipboard_server *server = wl_container_of( + listener, server, xwayland_ready); + + /* XWayland is ready, now we can start the startup command with proper DISPLAY */ + if (server->startup_cmd) { + /* Set the DISPLAY environment variable for X11 applications */ + if (server->xwayland->display_name) { + setenv("DISPLAY", server->xwayland->display_name, true); + printf("XWayland ready on DISPLAY=%s\n", server->xwayland->display_name); + } + + printf("Starting command: %s\n", server->startup_cmd); + if (fork() == 0) { + execl("/bin/sh", "/bin/sh", "-c", server->startup_cmd, (void *)NULL); + } + } else { + /* Even without startup command, announce XWayland is ready */ + if (server->xwayland->display_name) { + setenv("DISPLAY", server->xwayland->display_name, true); + printf("XWayland ready on DISPLAY=%s\n", server->xwayland->display_name); + } + } +} + +/* Basic compositor setup (reference from tinywl) */ +struct clipboard_output { + struct wl_list link; + struct clipboard_server *server; + struct wlr_output *wlr_output; + struct wl_listener frame; + struct wl_listener request_state; + struct wl_listener destroy; +}; + +struct clipboard_toplevel { + struct wl_list link; + struct clipboard_server *server; + struct wlr_xdg_toplevel *xdg_toplevel; + struct wlr_scene_tree *scene_tree; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener commit; + struct wl_listener destroy; + struct wl_listener request_move; + struct wl_listener request_resize; + struct wl_listener request_maximize; + struct wl_listener request_fullscreen; +}; + +struct clipboard_popup { + struct wlr_xdg_popup *xdg_popup; + struct wl_listener commit; + struct wl_listener destroy; +}; + +struct clipboard_keyboard { + struct wl_list link; + struct clipboard_server *server; + struct wlr_keyboard *wlr_keyboard; + + struct wl_listener modifiers; + struct wl_listener key; + struct wl_listener destroy; +}; + +/* Output event handlers (from tinywl) */ +static void output_frame(struct wl_listener *listener, void *data) { + struct clipboard_output *output = wl_container_of(listener, output, frame); + struct wlr_scene *scene = output->server->scene; + + struct wlr_scene_output *scene_output = wlr_scene_get_scene_output( + scene, output->wlr_output); + + wlr_scene_output_commit(scene_output, NULL); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(scene_output, &now); +} + +static void output_request_state(struct wl_listener *listener, void *data) { + struct clipboard_output *output = wl_container_of(listener, output, request_state); + const struct wlr_output_event_request_state *event = data; + wlr_output_commit_state(output->wlr_output, event->state); +} + +static void output_destroy(struct wl_listener *listener, void *data) { + struct clipboard_output *output = wl_container_of(listener, output, destroy); + + wl_list_remove(&output->frame.link); + wl_list_remove(&output->request_state.link); + wl_list_remove(&output->destroy.link); + wl_list_remove(&output->link); + free(output); +} + +static void server_new_output(struct wl_listener *listener, void *data) { + struct clipboard_server *server = wl_container_of(listener, server, new_output); + struct wlr_output *wlr_output = data; + + wlr_output_init_render(wlr_output, server->allocator, server->renderer); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + + struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + struct clipboard_output *output = calloc(1, sizeof(*output)); + output->wlr_output = wlr_output; + output->server = server; + + output->frame.notify = output_frame; + wl_signal_add(&wlr_output->events.frame, &output->frame); + + output->request_state.notify = output_request_state; + wl_signal_add(&wlr_output->events.request_state, &output->request_state); + + output->destroy.notify = output_destroy; + wl_signal_add(&wlr_output->events.destroy, &output->destroy); + + wl_list_insert(&server->outputs, &output->link); + + struct wlr_output_layout_output *l_output = wlr_output_layout_add_auto(server->output_layout, + wlr_output); + struct wlr_scene_output *scene_output = wlr_scene_output_create(server->scene, wlr_output); + wlr_scene_output_layout_add_output(server->scene_layout, l_output, scene_output); +} + +/* Focus management (from tinywl) */ +static void focus_xwayland_surface(struct clipboard_xwayland_surface *xwayland_surface) { + if (xwayland_surface == NULL || !xwayland_surface->xsurface || + !xwayland_surface->xsurface->surface) { + return; + } + + struct clipboard_server *server = xwayland_surface->server; + struct wlr_seat *seat = server->seat; + struct wlr_surface *surface = xwayland_surface->xsurface->surface; + struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; + + if (prev_surface == surface) { + return; + } + + // Deactivate previous surface + if (prev_surface) { + struct wlr_xdg_toplevel *prev_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(prev_surface); + if (prev_toplevel != NULL) { + wlr_xdg_toplevel_set_activated(prev_toplevel, false); + } + // Also check for XWayland surfaces + struct wlr_xwayland_surface *prev_xsurface = + wlr_xwayland_surface_try_from_wlr_surface(prev_surface); + if (prev_xsurface != NULL) { + wlr_xwayland_surface_activate(prev_xsurface, false); + } + } + + // Activate the XWayland surface + wlr_xwayland_surface_activate(xwayland_surface->xsurface, true); + + // Raise the XWayland surface to front by moving its scene node + if (xwayland_surface->scene_surface) { + wlr_scene_node_raise_to_top(&xwayland_surface->scene_surface->buffer->node); + } + + // Set keyboard focus + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); + if (keyboard != NULL) { + wlr_seat_keyboard_notify_enter(seat, surface, + keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); + } + + printf("Focused XWayland surface: %s\n", + xwayland_surface->xsurface->title ? xwayland_surface->xsurface->title : "unknown"); +} + +static void focus_toplevel(struct clipboard_toplevel *toplevel) { + if (toplevel == NULL) { + return; + } + struct clipboard_server *server = toplevel->server; + struct wlr_seat *seat = server->seat; + struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; + struct wlr_surface *surface = toplevel->xdg_toplevel->base->surface; + if (prev_surface == surface) { + return; + } + if (prev_surface) { + struct wlr_xdg_toplevel *prev_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(prev_surface); + if (prev_toplevel != NULL) { + wlr_xdg_toplevel_set_activated(prev_toplevel, false); + } + // Also check for XWayland surfaces + struct wlr_xwayland_surface *prev_xsurface = + wlr_xwayland_surface_try_from_wlr_surface(prev_surface); + if (prev_xsurface != NULL) { + wlr_xwayland_surface_activate(prev_xsurface, false); + } + } + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); + wlr_scene_node_raise_to_top(&toplevel->scene_tree->node); + wl_list_remove(&toplevel->link); + wl_list_insert(&server->toplevels, &toplevel->link); + wlr_xdg_toplevel_set_activated(toplevel->xdg_toplevel, true); + if (keyboard != NULL) { + wlr_seat_keyboard_notify_enter(seat, surface, + keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); + } +} + +/* Desktop interaction helpers */ +static struct clipboard_xwayland_surface *desktop_xwayland_surface_at( + struct clipboard_server *server, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) { + struct wlr_scene_node *node = wlr_scene_node_at( + &server->scene->tree.node, lx, ly, sx, sy); + if (node == NULL || node->type != WLR_SCENE_NODE_BUFFER) { + return NULL; + } + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(scene_buffer); + if (!scene_surface) { + return NULL; + } + + *surface = scene_surface->surface; + + // Check if this scene_surface belongs to an XWayland surface + if (scene_buffer->node.data) { + struct clipboard_xwayland_surface *xwayland_surface = + (struct clipboard_xwayland_surface *)scene_buffer->node.data; + // Verify this is actually an XWayland surface by checking the structure + if (xwayland_surface->xsurface && xwayland_surface->scene_surface == scene_surface) { + return xwayland_surface; + } + } + + return NULL; +} + +static struct clipboard_toplevel *desktop_toplevel_at( + struct clipboard_server *server, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) { + struct wlr_scene_node *node = wlr_scene_node_at( + &server->scene->tree.node, lx, ly, sx, sy); + if (node == NULL || node->type != WLR_SCENE_NODE_BUFFER) { + return NULL; + } + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(scene_buffer); + if (!scene_surface) { + return NULL; + } + + *surface = scene_surface->surface; + struct wlr_scene_tree *tree = node->parent; + while (tree != NULL && tree->node.data == NULL) { + tree = tree->node.parent; + } + + // Check if tree is NULL before accessing its data + if (tree == NULL) { + return NULL; + } + + return tree->node.data; +} + +static void reset_cursor_mode(struct clipboard_server *server) { + server->cursor_mode = CLIPBOARD_CURSOR_PASSTHROUGH; + server->grabbed_toplevel = NULL; +} + +static void process_cursor_move(struct clipboard_server *server) { + struct clipboard_toplevel *toplevel = server->grabbed_toplevel; + wlr_scene_node_set_position(&toplevel->scene_tree->node, + server->cursor->x - server->grab_x, + server->cursor->y - server->grab_y); +} + +static void process_cursor_motion(struct clipboard_server *server, uint32_t time) { + if (server->cursor_mode == CLIPBOARD_CURSOR_MOVE) { + process_cursor_move(server); + return; + } + + double sx, sy; + struct wlr_seat *seat = server->seat; + struct wlr_surface *surface = NULL; + struct clipboard_toplevel *toplevel = desktop_toplevel_at(server, + server->cursor->x, server->cursor->y, &surface, &sx, &sy); + if (!toplevel) { + wlr_cursor_set_xcursor(server->cursor, server->cursor_mgr, "default"); + } + if (surface) { + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } else { + wlr_seat_pointer_clear_focus(seat); + } +} + +/* Cursor event handlers */ +static void server_cursor_motion(struct wl_listener *listener, void *data) { + struct clipboard_server *server = + wl_container_of(listener, server, cursor_motion); + struct wlr_pointer_motion_event *event = data; + wlr_cursor_move(server->cursor, &event->pointer->base, + event->delta_x, event->delta_y); + process_cursor_motion(server, event->time_msec); +} + +static void server_cursor_motion_absolute( + struct wl_listener *listener, void *data) { + struct clipboard_server *server = + wl_container_of(listener, server, cursor_motion_absolute); + struct wlr_pointer_motion_absolute_event *event = data; + wlr_cursor_warp_absolute(server->cursor, &event->pointer->base, event->x, + event->y); + process_cursor_motion(server, event->time_msec); +} + +static void server_cursor_button(struct wl_listener *listener, void *data) { + struct clipboard_server *server = + wl_container_of(listener, server, cursor_button); + struct wlr_pointer_button_event *event = data; + wlr_seat_pointer_notify_button(server->seat, + event->time_msec, event->button, event->state); + if (event->state == WL_POINTER_BUTTON_STATE_RELEASED) { + reset_cursor_mode(server); + } else { + double sx, sy; + struct wlr_surface *surface = NULL; + + // First check for XWayland surfaces + struct clipboard_xwayland_surface *xwayland_surface = desktop_xwayland_surface_at(server, + server->cursor->x, server->cursor->y, &surface, &sx, &sy); + if (xwayland_surface) { + focus_xwayland_surface(xwayland_surface); + } else { + // Then check for regular toplevel surfaces + struct clipboard_toplevel *toplevel = desktop_toplevel_at(server, + server->cursor->x, server->cursor->y, &surface, &sx, &sy); + focus_toplevel(toplevel); + } + } +} + +static void server_cursor_axis(struct wl_listener *listener, void *data) { + struct clipboard_server *server = + wl_container_of(listener, server, cursor_axis); + struct wlr_pointer_axis_event *event = data; + wlr_seat_pointer_notify_axis(server->seat, + event->time_msec, event->orientation, event->delta, + event->delta_discrete, event->source, event->relative_direction); +} + +static void server_cursor_frame(struct wl_listener *listener, void *data) { + struct clipboard_server *server = + wl_container_of(listener, server, cursor_frame); + wlr_seat_pointer_notify_frame(server->seat); +} + +/* Toplevel event handlers */ +static void xdg_toplevel_map(struct wl_listener *listener, void *data) { + struct clipboard_toplevel *toplevel = wl_container_of(listener, toplevel, map); + wl_list_insert(&toplevel->server->toplevels, &toplevel->link); + focus_toplevel(toplevel); +} + +static void xdg_toplevel_unmap(struct wl_listener *listener, void *data) { + struct clipboard_toplevel *toplevel = wl_container_of(listener, toplevel, unmap); + if (toplevel == toplevel->server->grabbed_toplevel) { + reset_cursor_mode(toplevel->server); + } + wl_list_remove(&toplevel->link); +} + +static void xdg_toplevel_commit(struct wl_listener *listener, void *data) { + struct clipboard_toplevel *toplevel = wl_container_of(listener, toplevel, commit); + if (toplevel->xdg_toplevel->base->initial_commit) { + wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, 0, 0); + } +} + +static void xdg_toplevel_destroy(struct wl_listener *listener, void *data) { + struct clipboard_toplevel *toplevel = wl_container_of(listener, toplevel, destroy); + + wl_list_remove(&toplevel->map.link); + wl_list_remove(&toplevel->unmap.link); + wl_list_remove(&toplevel->commit.link); + wl_list_remove(&toplevel->destroy.link); + wl_list_remove(&toplevel->request_move.link); + wl_list_remove(&toplevel->request_resize.link); + wl_list_remove(&toplevel->request_maximize.link); + wl_list_remove(&toplevel->request_fullscreen.link); + + free(toplevel); +} + +static void begin_interactive(struct clipboard_toplevel *toplevel, + enum clipboard_cursor_mode mode, uint32_t edges) { + struct clipboard_server *server = toplevel->server; + + server->grabbed_toplevel = toplevel; + server->cursor_mode = mode; + + if (mode == CLIPBOARD_CURSOR_MOVE) { + server->grab_x = server->cursor->x - toplevel->scene_tree->node.x; + server->grab_y = server->cursor->y - toplevel->scene_tree->node.y; + } +} + +static void xdg_toplevel_request_move( + struct wl_listener *listener, void *data) { + struct clipboard_toplevel *toplevel = wl_container_of(listener, toplevel, request_move); + begin_interactive(toplevel, CLIPBOARD_CURSOR_MOVE, 0); +} + +static void xdg_toplevel_request_resize( + struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_resize_event *event = data; + struct clipboard_toplevel *toplevel = wl_container_of(listener, toplevel, request_resize); + begin_interactive(toplevel, CLIPBOARD_CURSOR_RESIZE, event->edges); +} + +static void xdg_toplevel_request_maximize( + struct wl_listener *listener, void *data) { + struct clipboard_toplevel *toplevel = + wl_container_of(listener, toplevel, request_maximize); + if (toplevel->xdg_toplevel->base->initialized) { + wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base); + } +} + +static void xdg_toplevel_request_fullscreen( + struct wl_listener *listener, void *data) { + struct clipboard_toplevel *toplevel = + wl_container_of(listener, toplevel, request_fullscreen); + if (toplevel->xdg_toplevel->base->initialized) { + wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base); + } +} + +static void server_new_xdg_toplevel(struct wl_listener *listener, void *data) { + struct clipboard_server *server = wl_container_of(listener, server, new_xdg_toplevel); + struct wlr_xdg_toplevel *xdg_toplevel = data; + + struct clipboard_toplevel *toplevel = calloc(1, sizeof(*toplevel)); + toplevel->server = server; + toplevel->xdg_toplevel = xdg_toplevel; + toplevel->scene_tree = + wlr_scene_xdg_surface_create(&toplevel->server->scene->tree, xdg_toplevel->base); + toplevel->scene_tree->node.data = toplevel; + xdg_toplevel->base->data = toplevel->scene_tree; + + toplevel->map.notify = xdg_toplevel_map; + wl_signal_add(&xdg_toplevel->base->surface->events.map, &toplevel->map); + toplevel->unmap.notify = xdg_toplevel_unmap; + wl_signal_add(&xdg_toplevel->base->surface->events.unmap, &toplevel->unmap); + toplevel->commit.notify = xdg_toplevel_commit; + wl_signal_add(&xdg_toplevel->base->surface->events.commit, &toplevel->commit); + + toplevel->destroy.notify = xdg_toplevel_destroy; + wl_signal_add(&xdg_toplevel->events.destroy, &toplevel->destroy); + + toplevel->request_move.notify = xdg_toplevel_request_move; + wl_signal_add(&xdg_toplevel->events.request_move, &toplevel->request_move); + toplevel->request_resize.notify = xdg_toplevel_request_resize; + wl_signal_add(&xdg_toplevel->events.request_resize, &toplevel->request_resize); + toplevel->request_maximize.notify = xdg_toplevel_request_maximize; + wl_signal_add(&xdg_toplevel->events.request_maximize, &toplevel->request_maximize); + toplevel->request_fullscreen.notify = xdg_toplevel_request_fullscreen; + wl_signal_add(&xdg_toplevel->events.request_fullscreen, &toplevel->request_fullscreen); +} + +/* Popup handlers */ +static void xdg_popup_commit(struct wl_listener *listener, void *data) { + struct clipboard_popup *popup = wl_container_of(listener, popup, commit); + if (popup->xdg_popup->base->initial_commit) { + wlr_xdg_surface_schedule_configure(popup->xdg_popup->base); + } +} + +static void xdg_popup_destroy(struct wl_listener *listener, void *data) { + struct clipboard_popup *popup = wl_container_of(listener, popup, destroy); + wl_list_remove(&popup->commit.link); + wl_list_remove(&popup->destroy.link); + free(popup); +} + +static void server_new_xdg_popup(struct wl_listener *listener, void *data) { + struct wlr_xdg_popup *xdg_popup = data; + + struct clipboard_popup *popup = calloc(1, sizeof(*popup)); + popup->xdg_popup = xdg_popup; + + struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface(xdg_popup->parent); + assert(parent != NULL); + struct wlr_scene_tree *parent_tree = parent->data; + xdg_popup->base->data = wlr_scene_xdg_surface_create(parent_tree, xdg_popup->base); + + popup->commit.notify = xdg_popup_commit; + wl_signal_add(&xdg_popup->base->surface->events.commit, &popup->commit); + + popup->destroy.notify = xdg_popup_destroy; + wl_signal_add(&xdg_popup->events.destroy, &popup->destroy); +} + +/* Input handlers */ +static void keyboard_handle_modifiers( + struct wl_listener *listener, void *data) { + struct clipboard_keyboard *keyboard = + wl_container_of(listener, keyboard, modifiers); + wlr_seat_set_keyboard(keyboard->server->seat, keyboard->wlr_keyboard); + wlr_seat_keyboard_notify_modifiers(keyboard->server->seat, + &keyboard->wlr_keyboard->modifiers); +} + +static bool handle_keybinding(struct clipboard_server *server, xkb_keysym_t sym) { + switch (sym) { + case XKB_KEY_Escape: + wl_display_terminate(server->wl_display); + break; + case XKB_KEY_F1: + if (wl_list_length(&server->toplevels) < 2) { + break; + } + struct clipboard_toplevel *next_toplevel = + wl_container_of(server->toplevels.prev, next_toplevel, link); + focus_toplevel(next_toplevel); + break; + default: + return false; + } + return true; +} + +static void keyboard_handle_key( + struct wl_listener *listener, void *data) { + struct clipboard_keyboard *keyboard = + wl_container_of(listener, keyboard, key); + struct clipboard_server *server = keyboard->server; + struct wlr_keyboard_key_event *event = data; + struct wlr_seat *seat = server->seat; + + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms( + keyboard->wlr_keyboard->xkb_state, keycode, &syms); + + bool handled = false; + + // First check if dialog is visible and handle Y/N keys + if (server->dialog_visible && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (int i = 0; i < nsyms; i++) { + if (syms[i] == XKB_KEY_y || syms[i] == XKB_KEY_Y) { + handle_dialog_response(server, true); + handled = true; + break; + } else if (syms[i] == XKB_KEY_n || syms[i] == XKB_KEY_N) { + handle_dialog_response(server, false); + handled = true; + break; + } + } + } + + if (!handled) { + uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->wlr_keyboard); + if ((modifiers & WLR_MODIFIER_ALT) && + event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (int i = 0; i < nsyms; i++) { + handled = handle_keybinding(server, syms[i]); + } + } + } + + if (!handled) { + wlr_seat_set_keyboard(seat, keyboard->wlr_keyboard); + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); + } +} + +static void keyboard_handle_destroy(struct wl_listener *listener, void *data) { + struct clipboard_keyboard *keyboard = + wl_container_of(listener, keyboard, destroy); + wl_list_remove(&keyboard->modifiers.link); + wl_list_remove(&keyboard->key.link); + wl_list_remove(&keyboard->destroy.link); + wl_list_remove(&keyboard->link); + free(keyboard); +} + +static void server_new_keyboard(struct clipboard_server *server, + struct wlr_input_device *device) { + struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(device); + + struct clipboard_keyboard *keyboard = calloc(1, sizeof(*keyboard)); + keyboard->server = server; + keyboard->wlr_keyboard = wlr_keyboard; + + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, + XKB_KEYMAP_COMPILE_NO_FLAGS); + + wlr_keyboard_set_keymap(wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + wlr_keyboard_set_repeat_info(wlr_keyboard, 25, 600); + + keyboard->modifiers.notify = keyboard_handle_modifiers; + wl_signal_add(&wlr_keyboard->events.modifiers, &keyboard->modifiers); + keyboard->key.notify = keyboard_handle_key; + wl_signal_add(&wlr_keyboard->events.key, &keyboard->key); + keyboard->destroy.notify = keyboard_handle_destroy; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + + wlr_seat_set_keyboard(server->seat, keyboard->wlr_keyboard); + + wl_list_insert(&server->keyboards, &keyboard->link); +} + +static void server_new_pointer(struct clipboard_server *server, + struct wlr_input_device *device) { + wlr_cursor_attach_input_device(server->cursor, device); +} + +static void server_new_input(struct wl_listener *listener, void *data) { + struct clipboard_server *server = + wl_container_of(listener, server, new_input); + struct wlr_input_device *device = data; + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + server_new_keyboard(server, device); + break; + case WLR_INPUT_DEVICE_POINTER: + server_new_pointer(server, device); + break; + default: + break; + } + uint32_t caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&server->keyboards)) { + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + } + wlr_seat_set_capabilities(server->seat, caps); +} + +static void seat_request_cursor(struct wl_listener *listener, void *data) { + struct clipboard_server *server = wl_container_of( + listener, server, request_cursor); + struct wlr_seat_pointer_request_set_cursor_event *event = data; + struct wlr_seat_client *focused_client = + server->seat->pointer_state.focused_client; + if (focused_client == event->seat_client) { + wlr_cursor_set_surface(server->cursor, event->surface, + event->hotspot_x, event->hotspot_y); + } +} + +static void seat_pointer_focus_change(struct wl_listener *listener, void *data) { + struct clipboard_server *server = wl_container_of( + listener, server, pointer_focus_change); + struct wlr_seat_pointer_focus_change_event *event = data; + if (event->new_surface == NULL) { + wlr_cursor_set_xcursor(server->cursor, server->cursor_mgr, "default"); + } +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + char *startup_cmd = NULL; + + int c; + while ((c = getopt(argc, argv, "s:h")) != -1) { + switch (c) { + case 's': + startup_cmd = optarg; + break; + case 'h': + printf("Clipboard Control Compositor\n"); + printf("Usage: %s [-s startup command]\n\n", argv[0]); + printf("Options:\n"); + printf(" -s Execute command after starting the compositor\n"); + printf(" -h Show this help message\n\n"); + printf("Controls:\n"); + printf(" Alt+Esc Exit compositor\n"); + printf(" Alt+F1 Switch between windows\n"); + printf(" Mouse Click and drag to move windows\n\n"); + printf("Examples:\n"); + printf(" %s # Start without applications\n", argv[0]); + printf(" %s -s \"weston-terminal\" # Start with terminal\n", argv[0]); + printf(" %s -s \"weston-terminal & gedit\" # Start with multiple apps\n", argv[0]); + return 0; + default: + printf("Usage: %s [-s startup command]\n", argv[0]); + printf("Use -h for detailed help.\n"); + return 0; + } + } + if (optind < argc) { + printf("Usage: %s [-s startup command]\n", argv[0]); + return 0; + } + + printf("Starting clipboard control compositor...\n"); + printf("This compositor will show approval dialogs for all clipboard operations.\n\n"); + + struct clipboard_server server = {0}; + + server.wl_display = wl_display_create(); + server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.wl_display), NULL); + if (server.backend == NULL) { + wlr_log(WLR_ERROR, "failed to create wlr_backend"); + return 1; + } + + server.renderer = wlr_renderer_autocreate(server.backend); + if (server.renderer == NULL) { + wlr_log(WLR_ERROR, "failed to create wlr_renderer"); + return 1; + } + + wlr_renderer_init_wl_display(server.renderer, server.wl_display); + + server.allocator = wlr_allocator_autocreate(server.backend, server.renderer); + if (server.allocator == NULL) { + wlr_log(WLR_ERROR, "failed to create wlr_allocator"); + return 1; + } + + /* Create compositor protocols */ + server.compositor = wlr_compositor_create(server.wl_display, 5, server.renderer); + wlr_subcompositor_create(server.wl_display); + wlr_data_device_manager_create(server.wl_display); + server.primary_selection_manager = + wlr_primary_selection_v1_device_manager_create(server.wl_display); + + server.output_layout = wlr_output_layout_create(server.wl_display); + + wl_list_init(&server.outputs); + server.new_output.notify = server_new_output; + wl_signal_add(&server.backend->events.new_output, &server.new_output); + + server.scene = wlr_scene_create(); + server.scene_layout = wlr_scene_attach_output_layout(server.scene, server.output_layout); + + wl_list_init(&server.toplevels); + server.xdg_shell = wlr_xdg_shell_create(server.wl_display, 3); + server.new_xdg_toplevel.notify = server_new_xdg_toplevel; + wl_signal_add(&server.xdg_shell->events.new_toplevel, &server.new_xdg_toplevel); + server.new_xdg_popup.notify = server_new_xdg_popup; + wl_signal_add(&server.xdg_shell->events.new_popup, &server.new_xdg_popup); + + server.cursor = wlr_cursor_create(); + wlr_cursor_attach_output_layout(server.cursor, server.output_layout); + server.cursor_mgr = wlr_xcursor_manager_create(NULL, 24); + + server.cursor_mode = CLIPBOARD_CURSOR_PASSTHROUGH; + server.cursor_motion.notify = server_cursor_motion; + wl_signal_add(&server.cursor->events.motion, &server.cursor_motion); + server.cursor_motion_absolute.notify = server_cursor_motion_absolute; + wl_signal_add(&server.cursor->events.motion_absolute, + &server.cursor_motion_absolute); + server.cursor_button.notify = server_cursor_button; + wl_signal_add(&server.cursor->events.button, &server.cursor_button); + server.cursor_axis.notify = server_cursor_axis; + wl_signal_add(&server.cursor->events.axis, &server.cursor_axis); + server.cursor_frame.notify = server_cursor_frame; + wl_signal_add(&server.cursor->events.frame, &server.cursor_frame); + + wl_list_init(&server.keyboards); + server.new_input.notify = server_new_input; + wl_signal_add(&server.backend->events.new_input, &server.new_input); + server.seat = wlr_seat_create(server.wl_display, "seat0"); + server.request_cursor.notify = seat_request_cursor; + wl_signal_add(&server.seat->events.request_set_cursor, + &server.request_cursor); + server.pointer_focus_change.notify = seat_pointer_focus_change; + wl_signal_add(&server.seat->pointer_state.events.focus_change, + &server.pointer_focus_change); + + /* Set up clipboard control */ + server.request_set_selection.notify = seat_request_set_selection; + wl_signal_add(&server.seat->events.request_set_selection, + &server.request_set_selection); + server.request_set_primary_selection.notify = seat_request_set_primary_selection; + wl_signal_add(&server.seat->events.request_set_primary_selection, + &server.request_set_primary_selection); + server.request_start_drag.notify = seat_request_start_drag; + wl_signal_add(&server.seat->events.request_start_drag, + &server.request_start_drag); + server.start_drag.notify = seat_start_drag; + wl_signal_add(&server.seat->events.start_drag, + &server.start_drag); + wl_list_init(&server.pending_requests); + + /* Initialize wrapper caches */ + wl_list_init(&server.active_data_source_wrappers); + wl_list_init(&server.active_primary_source_wrappers); + + /* Initialize dialog system */ + server.dialog_visible = false; + server.dialog_buffer = NULL; + server.current_request = NULL; + server.dialog_wlr_buffer = NULL; + + /* Create XWayland instance (non-lazy for this example) */ + server.xwayland = wlr_xwayland_create(server.wl_display, server.compositor, false); + if (!server.xwayland) { + wlr_log(WLR_ERROR, "Cannot create XWayland server"); + return 1; + } + + /* Set the seat for XWayland - required for clipboard operations */ + wlr_xwayland_set_seat(server.xwayland, server.seat); + printf("Set seat for XWayland server\n"); + + /* Set up XWayland surface listener */ + server.xwayland_new_surface.notify = server_new_xwayland_surface; + wl_signal_add(&server.xwayland->events.new_surface, &server.xwayland_new_surface); + + /* Store startup command and set up XWayland ready listener */ + server.startup_cmd = startup_cmd; + server.xwayland_ready.notify = xwayland_ready; + wl_signal_add(&server.xwayland->events.ready, &server.xwayland_ready); + + const char *socket = wl_display_add_socket_auto(server.wl_display); + if (!socket) { + wlr_backend_destroy(server.backend); + return 1; + } + + if (!wlr_backend_start(server.backend)) { + wlr_backend_destroy(server.backend); + wl_display_destroy(server.wl_display); + return 1; + } + + setenv("WAYLAND_DISPLAY", socket, true); + + printf("Running clipboard control compositor on WAYLAND_DISPLAY=%s\n", socket); + if (startup_cmd) { + printf("Waiting for XWayland to be ready before starting: %s\n", startup_cmd); + } + printf("All clipboard operations will require approval.\n"); + printf("Controls:\n"); + printf(" Alt+Esc: Exit compositor\n"); + printf(" Alt+F1: Switch between windows\n"); + printf(" Mouse: Click and drag to move windows\n"); + printf("Press Ctrl+C to exit.\n\n"); + + wl_display_run(server.wl_display); + + /* Cleanup */ + wl_display_destroy_clients(server.wl_display); + + wl_list_remove(&server.new_xdg_toplevel.link); + wl_list_remove(&server.new_xdg_popup.link); + + wl_list_remove(&server.cursor_motion.link); + wl_list_remove(&server.cursor_motion_absolute.link); + wl_list_remove(&server.cursor_button.link); + wl_list_remove(&server.cursor_axis.link); + wl_list_remove(&server.cursor_frame.link); + + wl_list_remove(&server.new_input.link); + wl_list_remove(&server.request_cursor.link); + wl_list_remove(&server.pointer_focus_change.link); + wl_list_remove(&server.request_set_selection.link); + wl_list_remove(&server.request_set_primary_selection.link); + wl_list_remove(&server.request_start_drag.link); + wl_list_remove(&server.start_drag.link); + + wl_list_remove(&server.new_output.link); + + if (server.xwayland) { + wl_list_remove(&server.xwayland_ready.link); + wl_list_remove(&server.xwayland_new_surface.link); + wlr_xwayland_destroy(server.xwayland); + } + + wlr_scene_node_destroy(&server.scene->tree.node); + wlr_xcursor_manager_destroy(server.cursor_mgr); + wlr_cursor_destroy(server.cursor); + wlr_allocator_destroy(server.allocator); + wlr_renderer_destroy(server.renderer); + wlr_backend_destroy(server.backend); + wl_display_destroy(server.wl_display); + + return 0; +} diff --git a/examples/meson.build b/examples/meson.build index 28a83cccc..b8a005d02 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -47,6 +47,11 @@ compositors = { ], 'dep': [wayland_client, wayland_egl, egl, glesv2], }, + 'clipboard-control': { + 'src': 'clipboard-control.c', + 'dep': [cairo], + 'proto': ['xdg-shell'], + }, } foreach name, info : compositors diff --git a/include/wlr/types/wlr_data_device.h b/include/wlr/types/wlr_data_device.h index c36b1a5cb..92fea03a7 100644 --- a/include/wlr/types/wlr_data_device.h +++ b/include/wlr/types/wlr_data_device.h @@ -11,6 +11,7 @@ #include #include +#include struct wlr_data_device_manager { struct wl_global *global; @@ -42,6 +43,9 @@ struct wlr_data_offer { enum wl_data_device_manager_dnd_action preferred_action; bool in_ask; + /* Data receiver for this offer */ + struct wlr_data_receiver receiver; + struct { struct wl_listener source_destroy; } WLR_PRIVATE; @@ -53,15 +57,22 @@ struct wlr_data_offer { */ struct wlr_data_source_impl { void (*send)(struct wlr_data_source *source, const char *mime_type, - int32_t fd); + struct wlr_data_receiver *receiver); void (*accept)(struct wlr_data_source *source, uint32_t serial, - const char *mime_type); + const char *mime_type, struct wlr_data_receiver *receiver); + void (*cancelled)(struct wlr_data_source *source); void (*destroy)(struct wlr_data_source *source); void (*dnd_drop)(struct wlr_data_source *source); void (*dnd_finish)(struct wlr_data_source *source); void (*dnd_action)(struct wlr_data_source *source, enum wl_data_device_manager_dnd_action action); + + /** + * Returns the unwrapped source object. This source object maybe is a + * wrapper, its a proxy for the others source. + */ + struct wlr_data_source *(*get_original)(struct wlr_data_source *source); }; struct wlr_data_source { @@ -74,6 +85,10 @@ struct wlr_data_source { // source status bool accepted; + // source information + struct wl_client *client; + pid_t pid; // PID of the source process (for XWayland, X11 client PID) + // drag'n'drop status enum wl_data_device_manager_dnd_action current_dnd_action; uint32_t compositor_action; @@ -217,18 +232,23 @@ void wlr_data_source_init(struct wlr_data_source *source, const struct wlr_data_source_impl *impl); /** - * Sends the data as the specified MIME type over the passed file descriptor, - * then close it. + * Sends the data as the specified MIME type to the receiver, + * with receiver managing the file descriptor and callbacks. */ -void wlr_data_source_send(struct wlr_data_source *source, const char *mime_type, - int32_t fd); +void wlr_data_source_send(struct wlr_data_source *source, + const char *mime_type, struct wlr_data_receiver *receiver); /** * Notifies the data source that a target accepts one of the offered MIME types. * If a target doesn't accept any of the offered types, `mime_type` is NULL. */ -void wlr_data_source_accept(struct wlr_data_source *source, uint32_t serial, - const char *mime_type); +void wlr_data_source_accept(struct wlr_data_source *source, + uint32_t serial, const char *mime_type, struct wlr_data_receiver *receiver); + +/** + * Notifies the data source that the operation was cancelled. + */ +void wlr_data_source_cancelled(struct wlr_data_source *source); /** * Notifies the data source it is no longer valid and should be destroyed. That @@ -261,4 +281,17 @@ void wlr_data_source_dnd_finish(struct wlr_data_source *source); void wlr_data_source_dnd_action(struct wlr_data_source *source, enum wl_data_device_manager_dnd_action action); +/** + * Copy metadata from one data source to another. This is useful for implementing + * wrapper data sources that can filter MIME types or other metadata. + */ +void wlr_data_source_copy(struct wlr_data_source *dest, struct wlr_data_source *src); + +/** + * Returns the original source object, it isn't NULL if the source argument + * isn't NULL. + */ +struct wlr_data_source * +wlr_data_source_get_original(struct wlr_data_source *source); + #endif diff --git a/include/wlr/types/wlr_data_receiver.h b/include/wlr/types/wlr_data_receiver.h new file mode 100644 index 000000000..b9a351f86 --- /dev/null +++ b/include/wlr/types/wlr_data_receiver.h @@ -0,0 +1,95 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_DATA_RECEIVER_H +#define WLR_TYPES_WLR_DATA_RECEIVER_H + +#include +#include + +struct wlr_data_receiver; + +/** + * A data receiver implementation. All callbacks are optional. + */ +struct wlr_data_receiver_impl { + /** + * Called when the transfer is cancelled before completion. This should + * clean up any ongoing transfer state. The fd will be automatically + * closed after this callback returns. + */ + void (*cancelled)(struct wlr_data_receiver *receiver); + + /** + * Called when the receiver is being destroyed. This should free any + * resources allocated for the receiver implementation. + */ + void (*destroy)(struct wlr_data_receiver *receiver); +}; + +/** + * A receiver is the receiving side of a data transfer. It represents the + * target of clipboard, primary selection, or drag-and-drop operations. + * + * This abstraction unifies the handling of data transfers across different + * protocols (Wayland native, XWayland, etc.) and provides a consistent + * interface for permission systems and transfer tracking. + */ +struct wlr_data_receiver { + const struct wlr_data_receiver_impl *impl; + + /** + * File descriptor for data transfer. This is typically a pipe write end + * that will be passed to the data source for writing clipboard data. + */ + int fd; + + /** + * Process ID of the receiving client. For XWayland windows, this is the + * X11 client PID, not the XWayland server PID. For native Wayland clients, + * this is the client process PID. + */ + pid_t pid; + + /** + * The Wayland client associated with this receiver. For XWayland windows, + * this refers to the XWayland server client, not the X11 application. + * For native Wayland clients, this is the actual client. + */ + struct wl_client *client; + + struct { + struct wl_signal destroy; + } events; +}; + +/** + * Initialize a data receiver with the given implementation. The receiver + * should be embedded in a larger structure (e.g., wlr_data_offer) to manage + * its lifecycle properly. + */ +void wlr_data_receiver_init(struct wlr_data_receiver *receiver, + const struct wlr_data_receiver_impl *impl); + +/** + * Destroy a data receiver. This will emit the destroy signal, call the + * implementation's destroy callback if present, and close any open file + * descriptor. After calling this function, the receiver should not be used. + */ +void wlr_data_receiver_destroy(struct wlr_data_receiver *receiver); + +/** + * Notify the receiver that the transfer has been cancelled. This calls the + * cancelled callback if implemented, then automatically closes the file + * descriptor. Should be called when a transfer is aborted before completion, + * such as when a drag operation is cancelled or a selection is cleared. + */ +void wlr_data_receiver_cancelled(struct wlr_data_receiver *receiver); + + +#endif diff --git a/include/wlr/types/wlr_primary_selection.h b/include/wlr/types/wlr_primary_selection.h index af3d48490..34bc7c644 100644 --- a/include/wlr/types/wlr_primary_selection.h +++ b/include/wlr/types/wlr_primary_selection.h @@ -11,6 +11,7 @@ #include #include +#include struct wlr_primary_selection_source; @@ -19,8 +20,14 @@ struct wlr_primary_selection_source; */ struct wlr_primary_selection_source_impl { void (*send)(struct wlr_primary_selection_source *source, - const char *mime_type, int fd); + const char *mime_type, struct wlr_data_receiver *receiver); void (*destroy)(struct wlr_primary_selection_source *source); + + /** + * Returns the unwrapped source object. This source object maybe is a + * wrapper, its a proxy for the others source. + */ + struct wlr_primary_selection_source *(*get_original)(struct wlr_primary_selection_source *source); }; /** @@ -32,6 +39,10 @@ struct wlr_primary_selection_source { // source metadata struct wl_array mime_types; + // source information + struct wl_client *client; + pid_t pid; // PID of the source process (for XWayland, X11 client PID) + struct { struct wl_signal destroy; } events; @@ -45,8 +56,22 @@ void wlr_primary_selection_source_init( void wlr_primary_selection_source_destroy( struct wlr_primary_selection_source *source); void wlr_primary_selection_source_send( - struct wlr_primary_selection_source *source, const char *mime_type, - int fd); + struct wlr_primary_selection_source *source, + const char *mime_type, struct wlr_data_receiver *receiver); + +/** + * Copy metadata from one primary selection source to another. This is useful for implementing + * wrapper primary selection sources that can filter MIME types or other metadata. + */ +void wlr_primary_selection_source_copy(struct wlr_primary_selection_source *dest, + struct wlr_primary_selection_source *src); + +/** + * Returns the original primary selection source object, it isn't NULL if the source argument + * isn't NULL. + */ +struct wlr_primary_selection_source * +wlr_primary_selection_source_get_original(struct wlr_primary_selection_source *source); /** * Request setting the primary selection. If `client` is not null, then the diff --git a/include/xwayland/selection.h b/include/xwayland/selection.h index 308310232..66bf11a4a 100644 --- a/include/xwayland/selection.h +++ b/include/xwayland/selection.h @@ -4,6 +4,7 @@ #include #include #include +#include #define INCR_CHUNK_SIZE (64 * 1024) @@ -34,6 +35,12 @@ struct wlr_xwm_selection_transfer { int property_start; xcb_get_property_reply_t *property_reply; xcb_window_t incoming_window; + + // Data receiver reference for Wayland client (may be NULL) + struct wlr_data_receiver *wl_client_receiver; + + // Listener for receiver destruction + struct wl_listener receiver_destroy; }; struct wlr_xwm_selection { @@ -60,7 +67,7 @@ void xwm_selection_transfer_destroy_property_reply( struct wlr_xwm_selection_transfer *transfer); void xwm_selection_transfer_init(struct wlr_xwm_selection_transfer *transfer, struct wlr_xwm_selection *selection); -void xwm_selection_transfer_destroy( +void xwm_selection_transfer_destroy_incoming( struct wlr_xwm_selection_transfer *transfer); void xwm_selection_transfer_destroy_outgoing( @@ -88,6 +95,8 @@ bool primary_selection_source_is_xwayland( void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag); +pid_t get_x11_window_pid(xcb_connection_t *conn, xcb_window_t window); + void xwm_selection_init(struct wlr_xwm_selection *selection, struct wlr_xwm *xwm, xcb_atom_t atom); void xwm_selection_finish(struct wlr_xwm_selection *selection); diff --git a/types/data_device/wlr_data_device.c b/types/data_device/wlr_data_device.c index ad7966d36..aa4517e4c 100644 --- a/types/data_device/wlr_data_device.c +++ b/types/data_device/wlr_data_device.c @@ -37,6 +37,7 @@ static void data_device_set_selection(struct wl_client *client, if (source != NULL) { source->finalized = true; + assert(client == source->source.client); } struct wlr_data_source *wlr_source = diff --git a/types/data_device/wlr_data_offer.c b/types/data_device/wlr_data_offer.c index 4bb9d0c24..ad0e8d1ea 100644 --- a/types/data_device/wlr_data_offer.c +++ b/types/data_device/wlr_data_offer.c @@ -6,10 +6,21 @@ #include #include #include +#include #include #include #include "types/wlr_data_device.h" +// Empty destroy function for simple data receiver implementation +static void handle_data_receiver_simple_destroy(struct wlr_data_receiver *receiver) { + // No-op: receiver is embedded in another structure and will be freed with it +} + +// Simple data receiver implementation with no callbacks +static const struct wlr_data_receiver_impl wlr_data_receiver_simple_impl = { + .destroy = handle_data_receiver_simple_destroy, +}; + static const struct wl_data_offer_interface data_offer_impl; static struct wlr_data_offer *data_offer_from_resource( @@ -88,7 +99,7 @@ static void data_offer_handle_accept(struct wl_client *client, return; } - wlr_data_source_accept(offer->source, serial, mime_type); + wlr_data_source_accept(offer->source, serial, mime_type, &offer->receiver); } static void data_offer_handle_receive(struct wl_client *client, @@ -99,7 +110,11 @@ static void data_offer_handle_receive(struct wl_client *client, return; } - wlr_data_source_send(offer->source, mime_type, fd); + // Set receiver fd for this transfer + offer->receiver.fd = fd; + assert(client == offer->receiver.client); + + wlr_data_source_send(offer->source, mime_type, &offer->receiver); } static void data_offer_source_dnd_finish(struct wlr_data_offer *offer) { @@ -208,6 +223,8 @@ void data_offer_destroy(struct wlr_data_offer *offer) { } } + wlr_data_receiver_destroy(&offer->receiver); + // Make the resource inert wl_resource_set_user_data(offer->resource, NULL); @@ -261,6 +278,10 @@ struct wlr_data_offer *data_offer_create(struct wl_resource *device_resource, wl_resource_set_implementation(offer->resource, &data_offer_impl, offer, data_offer_handle_resource_destroy); + wlr_data_receiver_init(&offer->receiver, &wlr_data_receiver_simple_impl); + offer->receiver.client = client; + wl_client_get_credentials(client, &offer->receiver.pid, NULL, NULL); + switch (type) { case WLR_DATA_OFFER_SELECTION: wl_list_insert(&seat_client->seat->selection_offers, &offer->link); diff --git a/types/data_device/wlr_data_source.c b/types/data_device/wlr_data_source.c index 64b73c56e..77506a0eb 100644 --- a/types/data_device/wlr_data_source.c +++ b/types/data_device/wlr_data_source.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "types/wlr_data_device.h" @@ -16,21 +17,28 @@ void wlr_data_source_init(struct wlr_data_source *source, *source = (struct wlr_data_source){ .impl = impl, .actions = -1, + .pid = 0, }; wl_array_init(&source->mime_types); wl_signal_init(&source->events.destroy); } -void wlr_data_source_send(struct wlr_data_source *source, const char *mime_type, - int32_t fd) { - source->impl->send(source, mime_type, fd); +void wlr_data_source_send(struct wlr_data_source *source, + const char *mime_type, struct wlr_data_receiver *receiver) { + source->impl->send(source, mime_type, receiver); } -void wlr_data_source_accept(struct wlr_data_source *source, uint32_t serial, - const char *mime_type) { +void wlr_data_source_accept(struct wlr_data_source *source, + uint32_t serial, const char *mime_type, struct wlr_data_receiver *receiver) { source->accepted = (mime_type != NULL); if (source->impl->accept) { - source->impl->accept(source, serial, mime_type); + source->impl->accept(source, serial, mime_type, receiver); + } +} + +void wlr_data_source_cancelled(struct wlr_data_source *source) { + if (source->impl->cancelled) { + source->impl->cancelled(source); } } @@ -76,6 +84,61 @@ void wlr_data_source_dnd_action(struct wlr_data_source *source, } } +void wlr_data_source_copy(struct wlr_data_source *dest, struct wlr_data_source *src) { + if (dest == NULL || src == NULL) { + return; + } + + // Copy basic metadata + dest->client = src->client; + dest->pid = src->pid; + dest->accepted = src->accepted; + dest->actions = src->actions; + dest->current_dnd_action = src->current_dnd_action; + dest->compositor_action = src->compositor_action; + + /* Clear any existing mime types before copying */ + if (dest->mime_types.size > 0) { + char **p; + wl_array_for_each(p, &dest->mime_types) { + free(*p); + } + wl_array_release(&dest->mime_types); + wl_array_init(&dest->mime_types); + } + + // Copy MIME types from source + char **p; + wl_array_for_each(p, &src->mime_types) { + char **dest_p = wl_array_add(&dest->mime_types, sizeof(*dest_p)); + if (dest_p == NULL) { + wlr_log(WLR_ERROR, "Failed to add MIME type to destination"); + continue; + } + + char *mime_type_copy = strdup(*p); + if (mime_type_copy == NULL) { + wlr_log(WLR_ERROR, "Failed to copy MIME type"); + continue; + } + + *dest_p = mime_type_copy; + } +} + +struct wlr_data_source *wlr_data_source_get_original( + struct wlr_data_source *source) { + if (!source) { + return NULL; + } + + if (source->impl && source->impl->get_original) { + return source->impl->get_original(source); + } + + return source; +} + static const struct wl_data_source_interface data_source_impl; @@ -87,7 +150,7 @@ struct wlr_client_data_source *client_data_source_from_resource( } static void client_data_source_accept(struct wlr_data_source *wlr_source, - uint32_t serial, const char *mime_type); + uint32_t serial, const char *mime_type, struct wlr_data_receiver *receiver); static struct wlr_client_data_source *client_data_source_from_wlr_data_source( struct wlr_data_source *wlr_source) { @@ -97,18 +160,26 @@ static struct wlr_client_data_source *client_data_source_from_wlr_data_source( } static void client_data_source_accept(struct wlr_data_source *wlr_source, - uint32_t serial, const char *mime_type) { + uint32_t serial, const char *mime_type, struct wlr_data_receiver *receiver) { struct wlr_client_data_source *source = client_data_source_from_wlr_data_source(wlr_source); wl_data_source_send_target(source->resource, mime_type); } static void client_data_source_send(struct wlr_data_source *wlr_source, - const char *mime_type, int32_t fd) { + const char *mime_type, struct wlr_data_receiver *receiver) { struct wlr_client_data_source *source = client_data_source_from_wlr_data_source(wlr_source); - wl_data_source_send_send(source->resource, mime_type, fd); - close(fd); + + wl_data_source_send_send(source->resource, mime_type, receiver->fd); + close(receiver->fd); + receiver->fd = -1; +} + +static void client_data_source_cancelled(struct wlr_data_source *wlr_source) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + wl_data_source_send_cancelled(source->resource); } static void client_data_source_destroy(struct wlr_data_source *wlr_source) { @@ -254,6 +325,7 @@ struct wlr_client_data_source *client_data_source_create( source->impl.accept = client_data_source_accept; source->impl.send = client_data_source_send; + source->impl.cancelled = client_data_source_cancelled; source->impl.destroy = client_data_source_destroy; if (wl_resource_get_version(source->resource) >= @@ -270,5 +342,8 @@ struct wlr_client_data_source *client_data_source_create( } wlr_data_source_init(&source->source, &source->impl); + source->source.client = client; + wl_client_get_credentials(client, &source->source.pid, NULL, NULL); + return source; } diff --git a/types/meson.build b/types/meson.build index 402fd3e11..a3d01bfac 100644 --- a/types/meson.build +++ b/types/meson.build @@ -3,6 +3,7 @@ wlr_files += files( 'data_device/wlr_data_offer.c', 'data_device/wlr_data_source.c', 'data_device/wlr_drag.c', + 'wlr_data_receiver.c', 'ext_image_capture_source_v1/base.c', 'ext_image_capture_source_v1/output.c', 'ext_image_capture_source_v1/foreign_toplevel.c', diff --git a/types/wlr_data_control_v1.c b/types/wlr_data_control_v1.c index 2e26d703d..7d1c28c2f 100644 --- a/types/wlr_data_control_v1.c +++ b/types/wlr_data_control_v1.c @@ -4,12 +4,23 @@ #include #include #include +#include #include #include #include "wlr-data-control-unstable-v1-protocol.h" #define DATA_CONTROL_MANAGER_VERSION 2 +// Empty destroy function for simple data receiver implementation +static void handle_data_receiver_simple_destroy(struct wlr_data_receiver *receiver) { + // No-op: receiver is embedded in another structure and will be freed with it +} + +// Simple data receiver implementation with no callbacks +static const struct wlr_data_receiver_impl wlr_data_receiver_simple_impl = { + .destroy = handle_data_receiver_simple_destroy, +}; + struct data_control_source { struct wl_resource *resource; struct wl_array mime_types; @@ -124,11 +135,13 @@ static struct client_data_source * } static void client_source_send(struct wlr_data_source *wlr_source, - const char *mime_type, int fd) { + const char *mime_type, struct wlr_data_receiver *receiver) { struct client_data_source *source = client_data_source_from_source(wlr_source); - zwlr_data_control_source_v1_send_send(source->resource, mime_type, fd); - close(fd); + + zwlr_data_control_source_v1_send_send(source->resource, mime_type, receiver->fd); + close(receiver->fd); + receiver->fd = -1; } static void client_source_destroy(struct wlr_data_source *wlr_source) { @@ -172,11 +185,13 @@ static struct client_primary_selection_source * static void client_primary_selection_source_send( struct wlr_primary_selection_source *wlr_source, - const char *mime_type, int fd) { + const char *mime_type, struct wlr_data_receiver *receiver) { struct client_primary_selection_source *source = client_primary_selection_source_from_source(wlr_source); - zwlr_data_control_source_v1_send_send(source->resource, mime_type, fd); - close(fd); + + zwlr_data_control_source_v1_send_send(source->resource, mime_type, receiver->fd); + close(receiver->fd); + receiver->fd = -1; } static void client_primary_selection_source_destroy( @@ -208,6 +223,9 @@ struct data_offer { struct wl_resource *resource; struct wlr_data_control_device_v1 *device; bool is_primary; + + // Data receiver for this offer + struct wlr_data_receiver receiver; }; static void data_offer_destroy(struct data_offer *offer) { @@ -225,6 +243,8 @@ static void data_offer_destroy(struct data_offer *offer) { } wl_resource_set_user_data(offer->resource, NULL); + wlr_data_receiver_destroy(&offer->receiver); + free(offer); } @@ -251,20 +271,27 @@ static void offer_handle_receive(struct wl_client *client, return; } + // Set receiver fd for this transfer + offer->receiver.fd = fd; + assert(offer->receiver.client == client); + if (offer->is_primary) { if (device->seat->primary_selection_source == NULL) { close(fd); return; } + wlr_primary_selection_source_send( device->seat->primary_selection_source, - mime_type, fd); + mime_type, &offer->receiver); } else { if (device->seat->selection_source == NULL) { close(fd); return; } - wlr_data_source_send(device->seat->selection_source, mime_type, fd); + + wlr_data_source_send(device->seat->selection_source, + mime_type, &offer->receiver); } } @@ -306,6 +333,10 @@ static struct wl_resource *create_offer(struct wlr_data_control_device_v1 *devic offer->resource = resource; + wlr_data_receiver_init(&offer->receiver, &wlr_data_receiver_simple_impl); + offer->receiver.client = client; + wl_client_get_credentials(client, &offer->receiver.pid, NULL, NULL); + wl_resource_set_implementation(resource, &offer_impl, offer, offer_handle_resource_destroy); @@ -369,6 +400,8 @@ static void control_handle_set_selection(struct wl_client *client, struct wlr_data_source *wlr_source = &client_source->source; wlr_data_source_init(wlr_source, &client_source_impl); + wlr_source->client = client; + wl_client_get_credentials(client, &wlr_source->pid, NULL, NULL); source->active_source = wlr_source; wl_array_release(&wlr_source->mime_types); @@ -421,6 +454,8 @@ static void control_handle_set_primary_selection(struct wl_client *client, struct wlr_primary_selection_source *wlr_source = &client_source->source; wlr_primary_selection_source_init(wlr_source, &client_primary_selection_source_impl); + wlr_source->client = client; + wl_client_get_credentials(client, &wlr_source->pid, NULL, NULL); source->active_primary_source = wlr_source; wl_array_release(&wlr_source->mime_types); diff --git a/types/wlr_data_receiver.c b/types/wlr_data_receiver.c new file mode 100644 index 000000000..276c90028 --- /dev/null +++ b/types/wlr_data_receiver.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include + +void wlr_data_receiver_init(struct wlr_data_receiver *receiver, + const struct wlr_data_receiver_impl *impl) { + assert(receiver && impl); + *receiver = (struct wlr_data_receiver){ + .impl = impl, + .fd = -1, + .pid = 0, + .client = NULL, + }; + wl_signal_init(&receiver->events.destroy); +} + +void wlr_data_receiver_destroy(struct wlr_data_receiver *receiver) { + if (!receiver) { + return; + } + + wl_signal_emit_mutable(&receiver->events.destroy, receiver); + assert(wl_list_empty(&receiver->events.destroy.listener_list)); + + int fd = receiver->fd; + + if (receiver->impl && receiver->impl->destroy) { + receiver->impl->destroy(receiver); + } else { + free(receiver); + } + + if (fd >= 0 && fcntl(fd, F_GETFD) != -1) { + close(fd); + } +} + +void wlr_data_receiver_cancelled(struct wlr_data_receiver *receiver) { + if (!receiver) { + return; + } + + if (receiver->impl && receiver->impl->cancelled) { + receiver->impl->cancelled(receiver); + } + + // Close fd if still valid + if (receiver->fd >= 0 && fcntl(receiver->fd, F_GETFD) != -1) { + close(receiver->fd); + receiver->fd = -1; + } +} diff --git a/types/wlr_ext_data_control_v1.c b/types/wlr_ext_data_control_v1.c index 0c487e674..0bc8576fb 100644 --- a/types/wlr_ext_data_control_v1.c +++ b/types/wlr_ext_data_control_v1.c @@ -4,12 +4,23 @@ #include #include #include +#include #include #include #include "ext-data-control-v1-protocol.h" #define EXT_DATA_CONTROL_MANAGER_VERSION 1 +// Empty destroy function for simple data receiver implementation +static void handle_data_receiver_simple_destroy(struct wlr_data_receiver *receiver) { + // No-op: receiver is embedded in another structure and will be freed with it +} + +// Simple data receiver implementation with no callbacks +static const struct wlr_data_receiver_impl wlr_data_receiver_simple_impl = { + .destroy = handle_data_receiver_simple_destroy, +}; + struct data_control_source { struct wl_resource *resource; struct wl_array mime_types; @@ -124,11 +135,11 @@ static struct client_data_source * } static void client_source_send(struct wlr_data_source *wlr_source, - const char *mime_type, int fd) { + const char *mime_type, struct wlr_data_receiver *receiver) { struct client_data_source *source = client_data_source_from_source(wlr_source); - ext_data_control_source_v1_send_send(source->resource, mime_type, fd); - close(fd); + ext_data_control_source_v1_send_send(source->resource, mime_type, receiver->fd); + close(receiver->fd); } static void client_source_destroy(struct wlr_data_source *wlr_source) { @@ -172,11 +183,11 @@ static struct client_primary_selection_source * static void client_primary_selection_source_send( struct wlr_primary_selection_source *wlr_source, - const char *mime_type, int fd) { + const char *mime_type, struct wlr_data_receiver *receiver) { struct client_primary_selection_source *source = client_primary_selection_source_from_source(wlr_source); - ext_data_control_source_v1_send_send(source->resource, mime_type, fd); - close(fd); + ext_data_control_source_v1_send_send(source->resource, mime_type, receiver->fd); + close(receiver->fd); } static void client_primary_selection_source_destroy( @@ -208,6 +219,9 @@ struct data_offer { struct wl_resource *resource; struct wlr_ext_data_control_device_v1 *device; bool is_primary; + + // Data receiver for this offer + struct wlr_data_receiver receiver; }; static void data_offer_destroy(struct data_offer *offer) { @@ -225,6 +239,8 @@ static void data_offer_destroy(struct data_offer *offer) { } wl_resource_set_user_data(offer->resource, NULL); + + wlr_data_receiver_destroy(&offer->receiver); free(offer); } @@ -251,6 +267,9 @@ static void offer_handle_receive(struct wl_client *client, return; } + offer->receiver.fd = fd; + assert(offer->receiver.client == client); + if (offer->is_primary) { if (device->seat->primary_selection_source == NULL) { close(fd); @@ -258,13 +277,14 @@ static void offer_handle_receive(struct wl_client *client, } wlr_primary_selection_source_send( device->seat->primary_selection_source, - mime_type, fd); + mime_type, &offer->receiver); } else { if (device->seat->selection_source == NULL) { close(fd); return; } - wlr_data_source_send(device->seat->selection_source, mime_type, fd); + wlr_data_source_send(device->seat->selection_source, + mime_type, &offer->receiver); } } @@ -305,6 +325,9 @@ static struct wl_resource *create_offer(struct wlr_ext_data_control_device_v1 *d } offer->resource = resource; + wlr_data_receiver_init(&offer->receiver, &wlr_data_receiver_simple_impl); + offer->receiver.client = client; + wl_client_get_credentials(client, &offer->receiver.pid, NULL, NULL); wl_resource_set_implementation(resource, &offer_impl, offer, offer_handle_resource_destroy); @@ -369,6 +392,8 @@ static void control_handle_set_selection(struct wl_client *client, struct wlr_data_source *wlr_source = &client_source->source; wlr_data_source_init(wlr_source, &client_source_impl); + wlr_source->client = client; + wl_client_get_credentials(client, &wlr_source->pid, NULL, NULL); source->active_source = wlr_source; wl_array_release(&wlr_source->mime_types); @@ -421,6 +446,8 @@ static void control_handle_set_primary_selection(struct wl_client *client, struct wlr_primary_selection_source *wlr_source = &client_source->source; wlr_primary_selection_source_init(wlr_source, &client_primary_selection_source_impl); + wlr_source->client = client; + wl_client_get_credentials(client, &wlr_source->pid, NULL, NULL); source->active_primary_source = wlr_source; wl_array_release(&wlr_source->mime_types); diff --git a/types/wlr_primary_selection.c b/types/wlr_primary_selection.c index 1940a2d82..87193033d 100644 --- a/types/wlr_primary_selection.c +++ b/types/wlr_primary_selection.c @@ -39,11 +39,61 @@ void wlr_primary_selection_source_destroy( } void wlr_primary_selection_source_send( - struct wlr_primary_selection_source *source, const char *mime_type, - int32_t fd) { - source->impl->send(source, mime_type, fd); + struct wlr_primary_selection_source *source, + const char *mime_type, struct wlr_data_receiver *receiver) { + source->impl->send(source, mime_type, receiver); } +void wlr_primary_selection_source_copy(struct wlr_primary_selection_source *dest, + struct wlr_primary_selection_source *src) { + if (dest == NULL || src == NULL) { + return; + } + + dest->client = src->client; + dest->pid = src->pid; + + /* Clear any existing mime types before copying */ + if (dest->mime_types.size > 0) { + char **p; + wl_array_for_each(p, &dest->mime_types) { + free(*p); + } + wl_array_release(&dest->mime_types); + wl_array_init(&dest->mime_types); + } + + // Copy MIME types from source + char **p; + wl_array_for_each(p, &src->mime_types) { + char **dest_p = wl_array_add(&dest->mime_types, sizeof(*dest_p)); + if (dest_p == NULL) { + wlr_log(WLR_ERROR, "Failed to add MIME type to destination"); + continue; + } + + char *mime_type_copy = strdup(*p); + if (mime_type_copy == NULL) { + wlr_log(WLR_ERROR, "Failed to copy MIME type"); + continue; + } + + *dest_p = mime_type_copy; + } +} + +struct wlr_primary_selection_source *wlr_primary_selection_source_get_original( + struct wlr_primary_selection_source *source) { + if (!source) { + return NULL; + } + + if (source->impl && source->impl->get_original) { + return source->impl->get_original(source); + } + + return source; +} void wlr_seat_request_set_primary_selection(struct wlr_seat *seat, struct wlr_seat_client *client, diff --git a/types/wlr_primary_selection_v1.c b/types/wlr_primary_selection_v1.c index fe70b7784..ad6e0c5f7 100644 --- a/types/wlr_primary_selection_v1.c +++ b/types/wlr_primary_selection_v1.c @@ -5,15 +5,33 @@ #include #include #include +#include #include #include #include "primary-selection-unstable-v1-protocol.h" #define DEVICE_MANAGER_VERSION 1 +// Primary selection offer structure +struct primary_selection_offer { + struct wlr_primary_selection_v1_device *device; + + // Data receiver for this offer + struct wlr_data_receiver receiver; +}; + +// Primary selection receiver implementation +static void handle_primary_selection_receiver_destroy(struct wlr_data_receiver *receiver) { + // The receiver is embedded in primary_selection_offer, so don't free it +} + +static const struct wlr_data_receiver_impl primary_selection_receiver_impl = { + .destroy = handle_primary_selection_receiver_destroy, +}; + static const struct zwp_primary_selection_offer_v1_interface offer_impl; -static struct wlr_primary_selection_v1_device *device_from_offer_resource( +static struct primary_selection_offer *offer_from_resource( struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &zwp_primary_selection_offer_v1_interface, &offer_impl)); @@ -22,15 +40,19 @@ static struct wlr_primary_selection_v1_device *device_from_offer_resource( static void offer_handle_receive(struct wl_client *client, struct wl_resource *resource, const char *mime_type, int32_t fd) { - struct wlr_primary_selection_v1_device *device = - device_from_offer_resource(resource); - if (device == NULL || device->seat->primary_selection_source == NULL) { + struct primary_selection_offer *offer = offer_from_resource(resource); + if (offer == NULL || offer->device == NULL || + offer->device->seat->primary_selection_source == NULL) { close(fd); return; } - wlr_primary_selection_source_send(device->seat->primary_selection_source, - mime_type, fd); + // Set receiver fd for this transfer + offer->receiver.fd = fd; + assert(offer->receiver.client == client); + + wlr_primary_selection_source_send(offer->device->seat->primary_selection_source, + mime_type, &offer->receiver); } static void offer_handle_destroy(struct wl_client *client, @@ -45,6 +67,11 @@ static const struct zwp_primary_selection_offer_v1_interface offer_impl = { static void offer_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); + struct primary_selection_offer *offer = offer_from_resource(resource); + if (offer) { + wlr_data_receiver_destroy(&offer->receiver); + free(offer); + } } static struct wlr_primary_selection_v1_device *device_from_resource( @@ -64,7 +91,22 @@ static void create_offer(struct wl_resource *device_resource, wl_resource_post_no_memory(device_resource); return; } - wl_resource_set_implementation(resource, &offer_impl, device, + + // Create the offer structure with embedded receiver + struct primary_selection_offer *offer = calloc(1, sizeof(*offer)); + if (offer == NULL) { + wl_resource_destroy(resource); + wl_resource_post_no_memory(device_resource); + return; + } + + offer->device = device; + + wlr_data_receiver_init(&offer->receiver, &primary_selection_receiver_impl); + offer->receiver.client = client; + wl_client_get_credentials(client, &offer->receiver.pid, NULL, NULL); + + wl_resource_set_implementation(resource, &offer_impl, offer, offer_handle_resource_destroy); wl_list_insert(&device->offers, wl_resource_get_link(resource)); @@ -80,7 +122,8 @@ static void create_offer(struct wl_resource *device_resource, } static void destroy_offer(struct wl_resource *resource) { - if (device_from_offer_resource(resource) == NULL) { + struct primary_selection_offer *offer = offer_from_resource(resource); + if (offer == NULL) { return; } @@ -101,10 +144,12 @@ struct client_data_source { static void client_source_send( struct wlr_primary_selection_source *wlr_source, - const char *mime_type, int fd) { + const char *mime_type, struct wlr_data_receiver *receiver) { struct client_data_source *source = wl_container_of(wlr_source, source, source); - zwp_primary_selection_source_v1_send_send(source->resource, mime_type, fd); - close(fd); + + zwp_primary_selection_source_v1_send_send(source->resource, mime_type, receiver->fd); + close(receiver->fd); + receiver->fd = -1; } static void client_source_destroy( @@ -374,6 +419,8 @@ static void device_manager_handle_create_source(struct wl_client *client, return; } wlr_primary_selection_source_init(&source->source, &client_source_impl); + source->source.client = client; + wl_client_get_credentials(client, &source->source.pid, NULL, NULL); uint32_t version = wl_resource_get_version(manager_resource); source->resource = wl_resource_create(client, diff --git a/xwayland/selection/incoming.c b/xwayland/selection/incoming.c index 72f82c279..5ccadf32e 100644 --- a/xwayland/selection/incoming.c +++ b/xwayland/selection/incoming.c @@ -7,6 +7,7 @@ #include #include #include +#include "wlr/types/wlr_data_receiver.h" #include "xwayland/selection.h" #include "xwayland/xwm.h" @@ -42,6 +43,39 @@ xwm_selection_transfer_create_incoming(struct wlr_xwm_selection *selection) { return transfer; } +void xwm_selection_transfer_destroy_incoming( + struct wlr_xwm_selection_transfer *transfer) { + if (!transfer) { + return; + } + + xwm_selection_transfer_destroy_property_reply(transfer); + xwm_selection_transfer_remove_event_source(transfer); + xwm_selection_transfer_close_wl_client_fd(transfer); + + + if (transfer->incoming_window) { + struct wlr_xwm *xwm = transfer->selection->xwm; + xcb_destroy_window(xwm->xcb_conn, transfer->incoming_window); + xwm_schedule_flush(xwm); + } + + if (transfer->wl_client_receiver) { + wl_list_remove(&transfer->receiver_destroy.link); + wlr_data_receiver_destroy(transfer->wl_client_receiver); + } + + wl_list_remove(&transfer->link); + free(transfer); +} + +static void handle_incoming_receiver_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwm_selection_transfer *transfer = + wl_container_of(listener, transfer, receiver_destroy); + transfer->wl_client_receiver = NULL; + wl_list_remove(&transfer->receiver_destroy.link); +} + struct wlr_xwm_selection_transfer * xwm_selection_find_incoming_transfer_by_window( struct wlr_xwm_selection *selection, xcb_window_t window) { @@ -110,7 +144,7 @@ static int write_selection_property_to_wl_client(int fd, uint32_t mask, ssize_t len = write(fd, property + transfer->property_start, remainder); if (len == -1) { wlr_log_errno(WLR_ERROR, "write error to target fd %d", fd); - xwm_selection_transfer_destroy(transfer); + xwm_selection_transfer_destroy_incoming(transfer); return 0; } @@ -126,7 +160,7 @@ static int write_selection_property_to_wl_client(int fd, uint32_t mask, xwm_notify_ready_for_next_incr_chunk(transfer); } else { wlr_log(WLR_DEBUG, "transfer complete"); - xwm_selection_transfer_destroy(transfer); + xwm_selection_transfer_destroy_incoming(transfer); } return 0; @@ -171,7 +205,7 @@ void xwm_get_incr_chunk(struct wlr_xwm_selection_transfer *transfer) { xwm_write_selection_property_to_wl_client(transfer); } else { wlr_log(WLR_DEBUG, "incremental transfer complete"); - xwm_selection_transfer_destroy(transfer); + xwm_selection_transfer_destroy_incoming(transfer); } } @@ -195,7 +229,7 @@ static void xwm_selection_transfer_get_data( static void source_send(struct wlr_xwm_selection *selection, struct wl_array *mime_types, struct wl_array *mime_types_atoms, - const char *requested_mime_type, int fd) { + const char *requested_mime_type, struct wlr_data_receiver *receiver) { struct wlr_xwm *xwm = selection->xwm; xcb_atom_t *atoms = mime_types_atoms->data; @@ -215,7 +249,7 @@ static void source_send(struct wlr_xwm_selection *selection, if (!found) { wlr_log(WLR_DEBUG, "Cannot send X11 selection to Wayland: " "unsupported MIME type"); - close(fd); + wlr_data_receiver_cancelled(receiver); return; } @@ -223,10 +257,14 @@ static void source_send(struct wlr_xwm_selection *selection, xwm_selection_transfer_create_incoming(selection); if (!transfer) { wlr_log(WLR_ERROR, "Cannot create transfer"); - close(fd); + wlr_data_receiver_cancelled(receiver); return; } + // Listen for receiver destruction to clean up the reference + transfer->receiver_destroy.notify = handle_incoming_receiver_destroy; + wl_signal_add(&receiver->events.destroy, &transfer->receiver_destroy); + xcb_convert_selection(xwm->xcb_conn, transfer->incoming_window, selection->atom, @@ -236,8 +274,9 @@ static void source_send(struct wlr_xwm_selection *selection, xwm_schedule_flush(xwm); - fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK); - transfer->wl_client_fd = fd; + fcntl(receiver->fd, F_SETFL, O_WRONLY | O_NONBLOCK); + transfer->wl_client_fd = receiver->fd; + transfer->wl_client_receiver = receiver; } struct x11_data_source { @@ -250,24 +289,26 @@ static const struct wlr_data_source_impl data_source_impl; bool data_source_is_xwayland( struct wlr_data_source *wlr_source) { - return wlr_source->impl == &data_source_impl; + struct wlr_data_source *original = wlr_data_source_get_original(wlr_source); + return original->impl == &data_source_impl; } static struct x11_data_source *data_source_from_wlr_data_source( struct wlr_data_source *wlr_source) { assert(data_source_is_xwayland(wlr_source)); - struct x11_data_source *source = wl_container_of(wlr_source, source, base); + struct wlr_data_source *original = wlr_data_source_get_original(wlr_source); + struct x11_data_source *source = wl_container_of(original, source, base); return source; } static void data_source_send(struct wlr_data_source *wlr_source, - const char *mime_type, int32_t fd) { + const char *mime_type, struct wlr_data_receiver *receiver) { struct x11_data_source *source = data_source_from_wlr_data_source(wlr_source); struct wlr_xwm_selection *selection = source->selection; source_send(selection, &wlr_source->mime_types, &source->mime_types_atoms, - mime_type, fd); + mime_type, receiver); } static void data_source_destroy(struct wlr_data_source *wlr_source) { @@ -293,17 +334,18 @@ static const struct wlr_primary_selection_source_impl bool primary_selection_source_is_xwayland( struct wlr_primary_selection_source *wlr_source) { - return wlr_source->impl == &primary_selection_source_impl; + struct wlr_primary_selection_source *original = wlr_primary_selection_source_get_original(wlr_source); + return original->impl == &primary_selection_source_impl; } static void primary_selection_source_send( struct wlr_primary_selection_source *wlr_source, - const char *mime_type, int fd) { + const char *mime_type, struct wlr_data_receiver *receiver) { struct x11_primary_selection_source *source = wl_container_of(wlr_source, source, base); struct wlr_xwm_selection *selection = source->selection; source_send(selection, &wlr_source->mime_types, &source->mime_types_atoms, - mime_type, fd); + mime_type, receiver); } static void primary_selection_source_destroy( @@ -407,6 +449,10 @@ static void xwm_selection_get_targets(struct wlr_xwm_selection *selection) { } wlr_data_source_init(&source->base, &data_source_impl); + // Set client and PID for XWayland data sources + source->base.client = xwm->xwayland->server->client; + source->base.pid = get_x11_window_pid(xwm->xcb_conn, selection->owner); + source->selection = selection; wl_array_init(&source->mime_types_atoms); @@ -426,6 +472,10 @@ static void xwm_selection_get_targets(struct wlr_xwm_selection *selection) { wlr_primary_selection_source_init(&source->base, &primary_selection_source_impl); + // Set client and PID for XWayland primary selection sources + source->base.client = xwm->xwayland->server->client; + source->base.pid = get_x11_window_pid(xwm->xcb_conn, selection->owner); + source->selection = selection; wl_array_init(&source->mime_types_atoms); @@ -460,7 +510,7 @@ void xwm_handle_selection_notify(struct wlr_xwm *xwm, if (event->property == XCB_ATOM_NONE) { if (transfer) { wlr_log(WLR_ERROR, "convert selection failed"); - xwm_selection_transfer_destroy(transfer); + xwm_selection_transfer_destroy_incoming(transfer); } } else if (event->target == xwm->atoms[TARGETS]) { // No xwayland surface focused, deny access to clipboard diff --git a/xwayland/selection/outgoing.c b/xwayland/selection/outgoing.c index 795a4768b..6129b1220 100644 --- a/xwayland/selection/outgoing.c +++ b/xwayland/selection/outgoing.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -56,11 +57,22 @@ static int xwm_selection_flush_source_data( static void xwm_selection_transfer_start_outgoing( struct wlr_xwm_selection_transfer *transfer); +// Structure to contain wlr_data_receiver and transfer pointer for outgoing transfers +struct xwm_outgoing_receiver { + struct wlr_data_receiver receiver; + struct wlr_xwm_selection_transfer *transfer; +}; + void xwm_selection_transfer_destroy_outgoing( struct wlr_xwm_selection_transfer *transfer) { wl_list_remove(&transfer->link); wlr_log(WLR_DEBUG, "Destroying transfer %p", transfer); + // Destroy the receiver if it exists + if (transfer->wl_client_receiver) { + wlr_data_receiver_destroy(transfer->wl_client_receiver); + } + xwm_selection_transfer_remove_event_source(transfer); xwm_selection_transfer_close_wl_client_fd(transfer); wl_array_release(&transfer->source_data); @@ -180,32 +192,54 @@ void xwm_send_incr_chunk(struct wlr_xwm_selection_transfer *transfer) { } } -static void xwm_selection_source_send(struct wlr_xwm_selection *selection, - const char *mime_type, int32_t fd) { +static void handle_data_receiver_cancelled(struct wlr_data_receiver *receiver) { + struct xwm_outgoing_receiver *outgoing_receiver = + wl_container_of(receiver, outgoing_receiver, receiver); + struct wlr_xwm_selection_transfer *transfer = outgoing_receiver->transfer; + + // Handle failure by destroying the transfer + xwm_selection_transfer_destroy_outgoing(transfer); +} + +static void handle_data_receiver_destroy(struct wlr_data_receiver *receiver) { + struct xwm_outgoing_receiver *outgoing_receiver = + wl_container_of(receiver, outgoing_receiver, receiver); + outgoing_receiver->transfer->wl_client_receiver = NULL; + free(outgoing_receiver); +} + +static const struct wlr_data_receiver_impl outgoing_receiver_impl = { + .cancelled = handle_data_receiver_cancelled, + .destroy = handle_data_receiver_destroy, +}; + +static bool xwm_selection_source_send(struct wlr_xwm_selection *selection, + const char *mime_type, struct wlr_data_receiver *receiver) { if (selection == &selection->xwm->clipboard_selection) { struct wlr_data_source *source = selection->xwm->seat->selection_source; if (source != NULL) { - wlr_data_source_send(source, mime_type, fd); - return; + wlr_data_source_send(source, mime_type, receiver); + return true; } } else if (selection == &selection->xwm->primary_selection) { struct wlr_primary_selection_source *source = selection->xwm->seat->primary_selection_source; if (source != NULL) { - wlr_primary_selection_source_send(source, mime_type, fd); - return; + wlr_primary_selection_source_send(source, mime_type, receiver); + return true; } } else if (selection == &selection->xwm->dnd_selection) { struct wlr_data_source *source = selection->xwm->seat->drag_source; if (source != NULL) { - wlr_data_source_send(source, mime_type, fd); - return; + wlr_data_source_send(source, mime_type, receiver); + return true; } } wlr_log(WLR_DEBUG, "not sending selection: no selection source available"); + return false; } static void xwm_selection_transfer_start_outgoing( @@ -276,16 +310,17 @@ static bool xwm_selection_send_data(struct wlr_xwm_selection *selection, return false; } - xwm_selection_transfer_init(transfer, selection); - transfer->request = *req; - wl_array_init(&transfer->source_data); - int p[2]; if (pipe(p) == -1) { wlr_log_errno(WLR_ERROR, "pipe() failed"); + free(transfer); return false; } + xwm_selection_transfer_init(transfer, selection); + transfer->request = *req; + wl_array_init(&transfer->source_data); + fcntl(p[0], F_SETFD, FD_CLOEXEC); fcntl(p[0], F_SETFL, O_NONBLOCK); fcntl(p[1], F_SETFD, FD_CLOEXEC); @@ -293,10 +328,31 @@ static bool xwm_selection_send_data(struct wlr_xwm_selection *selection, transfer->wl_client_fd = p[0]; - wlr_log(WLR_DEBUG, "Sending Wayland selection %u to Xwayland window with " - "MIME type %s, target %u, transfer %p", req->target, mime_type, - req->target, transfer); - xwm_selection_source_send(selection, mime_type, p[1]); + // Create and initialize the outgoing receiver + struct xwm_outgoing_receiver *outgoing_receiver = calloc(1, sizeof(*outgoing_receiver)); + if (!outgoing_receiver) { + wlr_log(WLR_ERROR, "Failed to allocate outgoing receiver"); + close(p[0]); + close(p[1]); + free(transfer); + return false; + } + + outgoing_receiver->transfer = transfer; + struct wlr_data_receiver *receiver = &outgoing_receiver->receiver; + + wlr_data_receiver_init(receiver, &outgoing_receiver_impl); + receiver->fd = p[1]; + receiver->client = selection->xwm->xwayland->server->client; + + // Get X11 window PID + if (transfer->request.requestor != XCB_WINDOW_NONE) { + receiver->pid = get_x11_window_pid(selection->xwm->xcb_conn, transfer->request.requestor); + } else { + receiver->pid = 0; + } + + transfer->wl_client_receiver = receiver; // It seems that if we ever try to reply to a selection request after // another has been sent by the same requestor, the requestor never reads @@ -317,6 +373,14 @@ static bool xwm_selection_send_data(struct wlr_xwm_selection *selection, xwm_selection_transfer_start_outgoing(transfer); + wlr_log(WLR_DEBUG, "Sending Wayland selection %u to Xwayland window with " + "MIME type %s, target %u, transfer %p", req->target, mime_type, + req->target, transfer); + // Maybe the transfer object is destroyed following the receiver object afeter + // xwm_selection_source_send, so don't access any member of transfer after this call. + transfer = NULL; + xwm_selection_source_send(selection, mime_type, receiver); + return true; } diff --git a/xwayland/selection/selection.c b/xwayland/selection/selection.c index deb695e24..bd9339689 100644 --- a/xwayland/selection/selection.c +++ b/xwayland/selection/selection.c @@ -3,12 +3,58 @@ #include #include #include +#include #include #include #include +#include #include "xwayland/selection.h" #include "xwayland/xwm.h" +/* Get X11 window PID using XRes extension */ +pid_t get_x11_window_pid(xcb_connection_t *conn, xcb_window_t window) { + if (!conn || window == XCB_WINDOW_NONE) { + return 0; + } + + // Check if XRes extension is available + xcb_query_extension_cookie_t ext_cookie = xcb_query_extension(conn, + strlen("X-Resource"), "X-Resource"); + xcb_query_extension_reply_t *ext_reply = xcb_query_extension_reply(conn, ext_cookie, NULL); + if (!ext_reply || !ext_reply->present) { + free(ext_reply); + return 0; + } + free(ext_reply); + + // Create client ID spec for the window + xcb_res_client_id_spec_t spec; + spec.client = window; + spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID; + + // Query client IDs for the window + xcb_res_query_client_ids_cookie_t cookie = xcb_res_query_client_ids(conn, 1, &spec); + xcb_res_query_client_ids_reply_t *reply = xcb_res_query_client_ids_reply(conn, cookie, NULL); + if (!reply) { + return 0; + } + + pid_t pid = 0; + xcb_res_client_id_value_iterator_t iter = xcb_res_query_client_ids_ids_iterator(reply); + for (; iter.rem; xcb_res_client_id_value_next(&iter)) { + if (iter.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { + uint32_t *value = xcb_res_client_id_value_value(iter.data); + if (value && iter.data->length >= sizeof(uint32_t)) { + pid = *value; + break; + } + } + } + + free(reply); + return pid; +} + void xwm_selection_transfer_remove_event_source( struct wlr_xwm_selection_transfer *transfer) { if (transfer->event_source != NULL) { @@ -39,26 +85,6 @@ void xwm_selection_transfer_init(struct wlr_xwm_selection_transfer *transfer, }; } -void xwm_selection_transfer_destroy( - struct wlr_xwm_selection_transfer *transfer) { - if (!transfer) { - return; - } - - xwm_selection_transfer_destroy_property_reply(transfer); - xwm_selection_transfer_remove_event_source(transfer); - xwm_selection_transfer_close_wl_client_fd(transfer); - - if (transfer->incoming_window) { - struct wlr_xwm *xwm = transfer->selection->xwm; - xcb_destroy_window(xwm->xcb_conn, transfer->incoming_window); - xwm_schedule_flush(xwm); - } - - wl_list_remove(&transfer->link); - free(transfer); -} - xcb_atom_t xwm_mime_type_to_atom(struct wlr_xwm *xwm, char *mime_type) { if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) { return xwm->atoms[UTF8_STRING]; @@ -256,7 +282,7 @@ void xwm_selection_finish(struct wlr_xwm_selection *selection) { struct wlr_xwm_selection_transfer *incoming; wl_list_for_each_safe(incoming, tmp, &selection->incoming, link) { - xwm_selection_transfer_destroy(incoming); + xwm_selection_transfer_destroy_incoming(incoming); } xcb_destroy_window(selection->xwm->xcb_conn, selection->window);