1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_VIDEO_DRIVER_WAYLAND
25
26#include <fcntl.h>
27#include <unistd.h>
28#include <limits.h>
29#include <signal.h>
30
31#include "../../core/unix/SDL_poll.h"
32#include "../../events/SDL_events_c.h"
33#include "../SDL_clipboard_c.h"
34
35#include "SDL_waylandvideo.h"
36#include "SDL_waylanddatamanager.h"
37#include "primary-selection-unstable-v1-client-protocol.h"
38
39/* FIXME: This is arbitrary, but we want this to be less than a frame because
40 * any longer can potentially spin an infinite loop of PumpEvents (!)
41 */
42#define PIPE_TIMEOUT_NS SDL_MS_TO_NS(14)
43
44static ssize_t write_pipe(int fd, const void *buffer, size_t total_length, size_t *pos)
45{
46 int ready = 0;
47 ssize_t bytes_written = 0;
48 ssize_t length = total_length - *pos;
49
50 sigset_t sig_set;
51 sigset_t old_sig_set;
52 struct timespec zerotime = { 0 };
53
54 ready = SDL_IOReady(fd, SDL_IOR_WRITE, PIPE_TIMEOUT_NS);
55
56 sigemptyset(&sig_set);
57 sigaddset(&sig_set, SIGPIPE);
58
59#ifdef SDL_THREADS_DISABLED
60 sigprocmask(SIG_BLOCK, &sig_set, &old_sig_set);
61#else
62 pthread_sigmask(SIG_BLOCK, &sig_set, &old_sig_set);
63#endif
64
65 if (ready == 0) {
66 bytes_written = SDL_SetError("Pipe timeout");
67 } else if (ready < 0) {
68 bytes_written = SDL_SetError("Pipe select error");
69 } else {
70 if (length > 0) {
71 bytes_written = write(fd, (Uint8 *)buffer + *pos, SDL_min(length, PIPE_BUF));
72 }
73
74 if (bytes_written > 0) {
75 *pos += bytes_written;
76 }
77 }
78
79 sigtimedwait(&sig_set, 0, &zerotime);
80
81#ifdef SDL_THREADS_DISABLED
82 sigprocmask(SIG_SETMASK, &old_sig_set, NULL);
83#else
84 pthread_sigmask(SIG_SETMASK, &old_sig_set, NULL);
85#endif
86
87 return bytes_written;
88}
89
90static ssize_t read_pipe(int fd, void **buffer, size_t *total_length)
91{
92 int ready = 0;
93 void *output_buffer = NULL;
94 char temp[PIPE_BUF];
95 size_t new_buffer_length = 0;
96 ssize_t bytes_read = 0;
97 size_t pos = 0;
98
99 ready = SDL_IOReady(fd, SDL_IOR_READ, PIPE_TIMEOUT_NS);
100
101 if (ready == 0) {
102 bytes_read = SDL_SetError("Pipe timeout");
103 } else if (ready < 0) {
104 bytes_read = SDL_SetError("Pipe select error");
105 } else {
106 bytes_read = read(fd, temp, sizeof(temp));
107 }
108
109 if (bytes_read > 0) {
110 pos = *total_length;
111 *total_length += bytes_read;
112
113 new_buffer_length = *total_length + sizeof(Uint32);
114
115 if (!*buffer) {
116 output_buffer = SDL_malloc(new_buffer_length);
117 } else {
118 output_buffer = SDL_realloc(*buffer, new_buffer_length);
119 }
120
121 if (!output_buffer) {
122 bytes_read = -1;
123 } else {
124 SDL_memcpy((Uint8 *)output_buffer + pos, temp, bytes_read);
125 SDL_memset((Uint8 *)output_buffer + (new_buffer_length - sizeof(Uint32)), 0, sizeof(Uint32));
126
127 *buffer = output_buffer;
128 }
129 }
130
131 return bytes_read;
132}
133
134static SDL_MimeDataList *mime_data_list_find(struct wl_list *list,
135 const char *mime_type)
136{
137 SDL_MimeDataList *found = NULL;
138
139 SDL_MimeDataList *mime_list = NULL;
140 wl_list_for_each (mime_list, list, link) {
141 if (SDL_strcmp(mime_list->mime_type, mime_type) == 0) {
142 found = mime_list;
143 break;
144 }
145 }
146 return found;
147}
148
149static bool mime_data_list_add(struct wl_list *list,
150 const char *mime_type,
151 const void *buffer, size_t length)
152{
153 bool result = true;
154 size_t mime_type_length = 0;
155 SDL_MimeDataList *mime_data = NULL;
156 void *internal_buffer = NULL;
157
158 if (buffer) {
159 internal_buffer = SDL_malloc(length);
160 if (!internal_buffer) {
161 return false;
162 }
163 SDL_memcpy(internal_buffer, buffer, length);
164 }
165
166 mime_data = mime_data_list_find(list, mime_type);
167
168 if (!mime_data) {
169 mime_data = SDL_calloc(1, sizeof(*mime_data));
170 if (!mime_data) {
171 result = false;
172 } else {
173 WAYLAND_wl_list_insert(list, &(mime_data->link));
174
175 mime_type_length = SDL_strlen(mime_type) + 1;
176 mime_data->mime_type = SDL_malloc(mime_type_length);
177 if (!mime_data->mime_type) {
178 result = false;
179 } else {
180 SDL_memcpy(mime_data->mime_type, mime_type, mime_type_length);
181 }
182 }
183 }
184
185 if (mime_data && buffer && length > 0) {
186 if (mime_data->data) {
187 SDL_free(mime_data->data);
188 }
189 mime_data->data = internal_buffer;
190 mime_data->length = length;
191 } else {
192 SDL_free(internal_buffer);
193 }
194
195 return result;
196}
197
198static void mime_data_list_free(struct wl_list *list)
199{
200 SDL_MimeDataList *mime_data = NULL;
201 SDL_MimeDataList *next = NULL;
202
203 wl_list_for_each_safe (mime_data, next, list, link) {
204 if (mime_data->data) {
205 SDL_free(mime_data->data);
206 }
207 if (mime_data->mime_type) {
208 SDL_free(mime_data->mime_type);
209 }
210 SDL_free(mime_data);
211 }
212}
213
214static size_t Wayland_send_data(const void *data, size_t length, int fd)
215{
216 size_t result = 0;
217
218 if (length > 0 && data) {
219 while (write_pipe(fd, data, length, &result) > 0) {
220 // Just keep spinning
221 }
222 }
223 close(fd);
224
225 return result;
226}
227
228ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source, const char *mime_type, int fd)
229{
230 const void *data = NULL;
231 size_t length = 0;
232
233 if (source->callback) {
234 data = source->callback(source->userdata.data, mime_type, &length);
235 }
236
237 return Wayland_send_data(data, length, fd);
238}
239
240ssize_t Wayland_primary_selection_source_send(SDL_WaylandPrimarySelectionSource *source, const char *mime_type, int fd)
241{
242 const void *data = NULL;
243 size_t length = 0;
244
245 if (source->callback) {
246 data = source->callback(source->userdata.data, mime_type, &length);
247 }
248
249 return Wayland_send_data(data, length, fd);
250}
251
252void Wayland_data_source_set_callback(SDL_WaylandDataSource *source,
253 SDL_ClipboardDataCallback callback,
254 void *userdata,
255 Uint32 sequence)
256{
257 if (source) {
258 source->callback = callback;
259 source->userdata.sequence = sequence;
260 source->userdata.data = userdata;
261 }
262}
263
264void Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSource *source,
265 SDL_ClipboardDataCallback callback,
266 void *userdata)
267{
268 if (source) {
269 source->callback = callback;
270 source->userdata.sequence = 0;
271 source->userdata.data = userdata;
272 }
273}
274
275static void *Wayland_clone_data_buffer(const void *buffer, const size_t *len)
276{
277 void *clone = NULL;
278 if (*len > 0 && buffer) {
279 clone = SDL_malloc((*len)+sizeof(Uint32));
280 if (clone) {
281 SDL_memcpy(clone, buffer, *len);
282 SDL_memset((Uint8 *)clone + *len, 0, sizeof(Uint32));
283 }
284 }
285 return clone;
286}
287
288void *Wayland_data_source_get_data(SDL_WaylandDataSource *source,
289 const char *mime_type, size_t *length)
290{
291 void *buffer = NULL;
292 const void *internal_buffer;
293 *length = 0;
294
295 if (!source) {
296 SDL_SetError("Invalid data source");
297 } else if (source->callback) {
298 internal_buffer = source->callback(source->userdata.data, mime_type, length);
299 buffer = Wayland_clone_data_buffer(internal_buffer, length);
300 }
301
302 return buffer;
303}
304
305void *Wayland_primary_selection_source_get_data(SDL_WaylandPrimarySelectionSource *source,
306 const char *mime_type, size_t *length)
307{
308 void *buffer = NULL;
309 const void *internal_buffer;
310 *length = 0;
311
312 if (!source) {
313 SDL_SetError("Invalid primary selection source");
314 } else if (source->callback) {
315 internal_buffer = source->callback(source->userdata.data, mime_type, length);
316 buffer = Wayland_clone_data_buffer(internal_buffer, length);
317 }
318
319 return buffer;
320}
321
322void Wayland_data_source_destroy(SDL_WaylandDataSource *source)
323{
324 if (source) {
325 SDL_WaylandDataDevice *data_device = (SDL_WaylandDataDevice *)source->data_device;
326 if (data_device && (data_device->selection_source == source)) {
327 data_device->selection_source = NULL;
328 }
329 wl_data_source_destroy(source->source);
330 if (source->userdata.sequence) {
331 SDL_CancelClipboardData(source->userdata.sequence);
332 } else {
333 SDL_free(source->userdata.data);
334 }
335 SDL_free(source);
336 }
337}
338
339void Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource *source)
340{
341 if (source) {
342 SDL_WaylandPrimarySelectionDevice *primary_selection_device = (SDL_WaylandPrimarySelectionDevice *)source->primary_selection_device;
343 if (primary_selection_device && (primary_selection_device->selection_source == source)) {
344 primary_selection_device->selection_source = NULL;
345 }
346 zwp_primary_selection_source_v1_destroy(source->source);
347 if (source->userdata.sequence == 0) {
348 SDL_free(source->userdata.data);
349 }
350 SDL_free(source);
351 }
352}
353
354void *Wayland_data_offer_receive(SDL_WaylandDataOffer *offer,
355 const char *mime_type, size_t *length)
356{
357 SDL_WaylandDataDevice *data_device = NULL;
358
359 int pipefd[2];
360 void *buffer = NULL;
361 *length = 0;
362
363 if (!offer) {
364 SDL_SetError("Invalid data offer");
365 return NULL;
366 }
367 data_device = offer->data_device;
368 if (!data_device) {
369 SDL_SetError("Data device not initialized");
370 } else if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1) {
371 SDL_SetError("Could not read pipe");
372 } else {
373 wl_data_offer_receive(offer->offer, mime_type, pipefd[1]);
374 close(pipefd[1]);
375
376 WAYLAND_wl_display_flush(data_device->video_data->display);
377
378 while (read_pipe(pipefd[0], &buffer, length) > 0) {
379 }
380 close(pipefd[0]);
381 }
382 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
383 ". In Wayland_data_offer_receive for '%s', buffer (%zu) at %p",
384 mime_type, *length, buffer);
385 return buffer;
386}
387
388void *Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelectionOffer *offer,
389 const char *mime_type, size_t *length)
390{
391 SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
392
393 int pipefd[2];
394 void *buffer = NULL;
395 *length = 0;
396
397 if (!offer) {
398 SDL_SetError("Invalid data offer");
399 return NULL;
400 }
401 primary_selection_device = offer->primary_selection_device;
402 if (!primary_selection_device) {
403 SDL_SetError("Primary selection device not initialized");
404 } else if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1) {
405 SDL_SetError("Could not read pipe");
406 } else {
407 zwp_primary_selection_offer_v1_receive(offer->offer, mime_type, pipefd[1]);
408 close(pipefd[1]);
409
410 WAYLAND_wl_display_flush(primary_selection_device->video_data->display);
411
412 while (read_pipe(pipefd[0], &buffer, length) > 0) {
413 }
414 close(pipefd[0]);
415 }
416 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
417 ". In Wayland_primary_selection_offer_receive for '%s', buffer (%zu) at %p",
418 mime_type, *length, buffer);
419 return buffer;
420}
421
422bool Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer,
423 const char *mime_type)
424{
425 return mime_data_list_add(&offer->mimes, mime_type, NULL, 0);
426}
427
428bool Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer *offer,
429 const char *mime_type)
430{
431 return mime_data_list_add(&offer->mimes, mime_type, NULL, 0);
432}
433
434bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer,
435 const char *mime_type)
436{
437 bool found = false;
438
439 if (offer) {
440 found = mime_data_list_find(&offer->mimes, mime_type) != NULL;
441 }
442 return found;
443}
444
445bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *offer,
446 const char *mime_type)
447{
448 bool found = false;
449
450 if (offer) {
451 found = mime_data_list_find(&offer->mimes, mime_type) != NULL;
452 }
453 return found;
454}
455
456void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer)
457{
458 if (offer) {
459 wl_data_offer_destroy(offer->offer);
460 mime_data_list_free(&offer->mimes);
461 SDL_free(offer);
462 }
463}
464
465void Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionOffer *offer)
466{
467 if (offer) {
468 zwp_primary_selection_offer_v1_destroy(offer->offer);
469 mime_data_list_free(&offer->mimes);
470 SDL_free(offer);
471 }
472}
473
474bool Wayland_data_device_clear_selection(SDL_WaylandDataDevice *data_device)
475{
476 bool result = true;
477
478 if (!data_device || !data_device->data_device) {
479 result = SDL_SetError("Invalid Data Device");
480 } else if (data_device->selection_source) {
481 wl_data_device_set_selection(data_device->data_device, NULL, 0);
482 Wayland_data_source_destroy(data_device->selection_source);
483 data_device->selection_source = NULL;
484 }
485 return result;
486}
487
488bool Wayland_primary_selection_device_clear_selection(SDL_WaylandPrimarySelectionDevice *primary_selection_device)
489{
490 bool result = true;
491
492 if (!primary_selection_device || !primary_selection_device->primary_selection_device) {
493 result = SDL_SetError("Invalid Primary Selection Device");
494 } else if (primary_selection_device->selection_source) {
495 zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
496 NULL, 0);
497 Wayland_primary_selection_source_destroy(primary_selection_device->selection_source);
498 primary_selection_device->selection_source = NULL;
499 }
500 return result;
501}
502
503bool Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
504 SDL_WaylandDataSource *source,
505 const char **mime_types,
506 size_t mime_count)
507{
508 bool result = true;
509
510 if (!data_device) {
511 result = SDL_SetError("Invalid Data Device");
512 } else if (!source) {
513 result = SDL_SetError("Invalid source");
514 } else {
515 size_t index = 0;
516 const char *mime_type;
517
518 for (index = 0; index < mime_count; ++index) {
519 mime_type = mime_types[index];
520 wl_data_source_offer(source->source,
521 mime_type);
522 }
523
524 if (index == 0) {
525 Wayland_data_device_clear_selection(data_device);
526 result = SDL_SetError("No mime data");
527 } else {
528 // Only set if there is a valid serial if not set it later
529 if (data_device->selection_serial != 0) {
530 wl_data_device_set_selection(data_device->data_device,
531 source->source,
532 data_device->selection_serial);
533 }
534 if (data_device->selection_source) {
535 Wayland_data_source_destroy(data_device->selection_source);
536 }
537 data_device->selection_source = source;
538 source->data_device = data_device;
539 }
540 }
541
542 return result;
543}
544
545bool Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDevice *primary_selection_device,
546 SDL_WaylandPrimarySelectionSource *source,
547 const char **mime_types,
548 size_t mime_count)
549{
550 bool result = true;
551
552 if (!primary_selection_device) {
553 result = SDL_SetError("Invalid Primary Selection Device");
554 } else if (!source) {
555 result = SDL_SetError("Invalid source");
556 } else {
557 size_t index = 0;
558 const char *mime_type = mime_types[index];
559
560 for (index = 0; index < mime_count; ++index) {
561 mime_type = mime_types[index];
562 zwp_primary_selection_source_v1_offer(source->source, mime_type);
563 }
564
565 if (index == 0) {
566 Wayland_primary_selection_device_clear_selection(primary_selection_device);
567 result = SDL_SetError("No mime data");
568 } else {
569 // Only set if there is a valid serial if not set it later
570 if (primary_selection_device->selection_serial != 0) {
571 zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
572 source->source,
573 primary_selection_device->selection_serial);
574 }
575 if (primary_selection_device->selection_source) {
576 Wayland_primary_selection_source_destroy(primary_selection_device->selection_source);
577 }
578 primary_selection_device->selection_source = source;
579 source->primary_selection_device = primary_selection_device;
580 }
581 }
582
583 return result;
584}
585
586void Wayland_data_device_set_serial(SDL_WaylandDataDevice *data_device, uint32_t serial)
587{
588 if (data_device) {
589 // If there was no serial and there is a pending selection set it now.
590 if (data_device->selection_serial == 0 && data_device->selection_source) {
591 wl_data_device_set_selection(data_device->data_device,
592 data_device->selection_source->source,
593 data_device->selection_serial);
594 }
595
596 data_device->selection_serial = serial;
597 }
598}
599
600void Wayland_primary_selection_device_set_serial(SDL_WaylandPrimarySelectionDevice *primary_selection_device,
601 uint32_t serial)
602{
603 if (primary_selection_device) {
604 // If there was no serial and there is a pending selection set it now.
605 if (primary_selection_device->selection_serial == 0 && primary_selection_device->selection_source) {
606 zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
607 primary_selection_device->selection_source->source,
608 primary_selection_device->selection_serial);
609 }
610
611 primary_selection_device->selection_serial = serial;
612 }
613}
614
615#endif // SDL_VIDEO_DRIVER_WAYLAND
616