wlroots/examples/clipboard-control.c
JiDe Zhang 6745b7bd49 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.
2026-01-13 15:25:56 +08:00

1996 lines
64 KiB
C

#include <assert.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_primary_selection.h>
#include <wlr/types/wlr_primary_selection_v1.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include <wlr/xwayland/xwayland.h>
#include "xwayland/selection.h"
#include <xcb/xcb.h>
#include <xcb/res.h>
#include <xkbcommon/xkbcommon.h>
#include <cairo.h>
#include <drm_fourcc.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/interfaces/wlr_buffer.h>
/* 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 <command> 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;
}