1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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#if SDL_VIDEO_DRIVER_WAYLAND
25
26#include <fcntl.h>
27#include <unistd.h>
28#include <limits.h>
29#include <signal.h>
30
31#include "SDL_stdinc.h"
32#include "../../core/unix/SDL_poll.h"
33
34#include "SDL_waylandvideo.h"
35#include "SDL_waylanddatamanager.h"
36
37#include "SDL_waylanddyn.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_MS_TIMEOUT 10
43
44static ssize_t
45write_pipe(int fd, const void* buffer, size_t total_length, size_t *pos)
46{
47 int ready = 0;
48 ssize_t bytes_written = 0;
49 ssize_t length = total_length - *pos;
50
51 sigset_t sig_set;
52 sigset_t old_sig_set;
53 struct timespec zerotime = {0};
54
55 ready = SDL_IOReady(fd, SDL_TRUE, PIPE_MS_TIMEOUT);
56
57 sigemptyset(&sig_set);
58 sigaddset(&sig_set, SIGPIPE);
59
60#if SDL_THREADS_DISABLED
61 sigprocmask(SIG_BLOCK, &sig_set, &old_sig_set);
62#else
63 pthread_sigmask(SIG_BLOCK, &sig_set, &old_sig_set);
64#endif
65
66 if (ready == 0) {
67 bytes_written = SDL_SetError("Pipe timeout");
68 } else if (ready < 0) {
69 bytes_written = SDL_SetError("Pipe select error");
70 } else {
71 if (length > 0) {
72 bytes_written = write(fd, (Uint8*)buffer + *pos, SDL_min(length, PIPE_BUF));
73 }
74
75 if (bytes_written > 0) {
76 *pos += bytes_written;
77 }
78 }
79
80 sigtimedwait(&sig_set, 0, &zerotime);
81
82#if SDL_THREADS_DISABLED
83 sigprocmask(SIG_SETMASK, &old_sig_set, NULL);
84#else
85 pthread_sigmask(SIG_SETMASK, &old_sig_set, NULL);
86#endif
87
88 return bytes_written;
89}
90
91static ssize_t
92read_pipe(int fd, void** buffer, size_t* total_length, SDL_bool null_terminate)
93{
94 int ready = 0;
95 void* output_buffer = NULL;
96 char temp[PIPE_BUF];
97 size_t new_buffer_length = 0;
98 ssize_t bytes_read = 0;
99 size_t pos = 0;
100
101 ready = SDL_IOReady(fd, SDL_FALSE, PIPE_MS_TIMEOUT);
102
103 if (ready == 0) {
104 bytes_read = SDL_SetError("Pipe timeout");
105 } else if (ready < 0) {
106 bytes_read = SDL_SetError("Pipe select error");
107 } else {
108 bytes_read = read(fd, temp, sizeof(temp));
109 }
110
111 if (bytes_read > 0) {
112 pos = *total_length;
113 *total_length += bytes_read;
114
115 if (null_terminate == SDL_TRUE) {
116 new_buffer_length = *total_length + 1;
117 } else {
118 new_buffer_length = *total_length;
119 }
120
121 if (*buffer == NULL) {
122 output_buffer = SDL_malloc(new_buffer_length);
123 } else {
124 output_buffer = SDL_realloc(*buffer, new_buffer_length);
125 }
126
127 if (output_buffer == NULL) {
128 bytes_read = SDL_OutOfMemory();
129 } else {
130 SDL_memcpy((Uint8*)output_buffer + pos, temp, bytes_read);
131
132 if (null_terminate == SDL_TRUE) {
133 SDL_memset((Uint8*)output_buffer + (new_buffer_length - 1), 0, 1);
134 }
135
136 *buffer = output_buffer;
137 }
138 }
139
140 return bytes_read;
141}
142
143#define MIME_LIST_SIZE 4
144
145static const char* mime_conversion_list[MIME_LIST_SIZE][2] = {
146 {"text/plain", TEXT_MIME},
147 {"TEXT", TEXT_MIME},
148 {"UTF8_STRING", TEXT_MIME},
149 {"STRING", TEXT_MIME}
150};
151
152const char*
153Wayland_convert_mime_type(const char *mime_type)
154{
155 const char *found = mime_type;
156
157 size_t index = 0;
158
159 for (index = 0; index < MIME_LIST_SIZE; ++index) {
160 if (strcmp(mime_conversion_list[index][0], mime_type) == 0) {
161 found = mime_conversion_list[index][1];
162 break;
163 }
164 }
165
166 return found;
167}
168
169static SDL_MimeDataList*
170mime_data_list_find(struct wl_list* list,
171 const char* mime_type)
172{
173 SDL_MimeDataList *found = NULL;
174
175 SDL_MimeDataList *mime_list = NULL;
176 wl_list_for_each(mime_list, list, link) {
177 if (strcmp(mime_list->mime_type, mime_type) == 0) {
178 found = mime_list;
179 break;
180 }
181 }
182 return found;
183}
184
185static int
186mime_data_list_add(struct wl_list* list,
187 const char* mime_type,
188 const void* buffer, size_t length)
189{
190 int status = 0;
191 size_t mime_type_length = 0;
192 SDL_MimeDataList *mime_data = NULL;
193 void *internal_buffer = NULL;
194
195 if (buffer != NULL) {
196 internal_buffer = SDL_malloc(length);
197 if (internal_buffer == NULL) {
198 return SDL_OutOfMemory();
199 }
200 SDL_memcpy(internal_buffer, buffer, length);
201 }
202
203 mime_data = mime_data_list_find(list, mime_type);
204
205 if (mime_data == NULL) {
206 mime_data = SDL_calloc(1, sizeof(*mime_data));
207 if (mime_data == NULL) {
208 status = SDL_OutOfMemory();
209 } else {
210 WAYLAND_wl_list_insert(list, &(mime_data->link));
211
212 mime_type_length = SDL_strlen(mime_type) + 1;
213 mime_data->mime_type = SDL_malloc(mime_type_length);
214 if (mime_data->mime_type == NULL) {
215 status = SDL_OutOfMemory();
216 } else {
217 SDL_memcpy(mime_data->mime_type, mime_type, mime_type_length);
218 }
219 }
220 }
221
222 if (mime_data != NULL && buffer != NULL && length > 0) {
223 if (mime_data->data != NULL) {
224 SDL_free(mime_data->data);
225 }
226 mime_data->data = internal_buffer;
227 mime_data->length = length;
228 } else {
229 SDL_free(internal_buffer);
230 }
231
232 return status;
233}
234
235static void
236mime_data_list_free(struct wl_list *list)
237{
238 SDL_MimeDataList *mime_data = NULL;
239 SDL_MimeDataList *next = NULL;
240
241 wl_list_for_each_safe(mime_data, next, list, link) {
242 if (mime_data->data != NULL) {
243 SDL_free(mime_data->data);
244 }
245 if (mime_data->mime_type != NULL) {
246 SDL_free(mime_data->mime_type);
247 }
248 SDL_free(mime_data);
249 }
250}
251
252ssize_t
253Wayland_data_source_send(SDL_WaylandDataSource *source,
254 const char *mime_type, int fd)
255{
256 size_t written_bytes = 0;
257 ssize_t status = 0;
258 SDL_MimeDataList *mime_data = NULL;
259
260 mime_type = Wayland_convert_mime_type(mime_type);
261 mime_data = mime_data_list_find(&source->mimes,
262 mime_type);
263
264 if (mime_data == NULL || mime_data->data == NULL) {
265 status = SDL_SetError("Invalid mime type");
266 close(fd);
267 } else {
268 while (write_pipe(fd, mime_data->data, mime_data->length,
269 &written_bytes) > 0);
270 close(fd);
271 status = written_bytes;
272 }
273 return status;
274}
275
276int Wayland_data_source_add_data(SDL_WaylandDataSource *source,
277 const char *mime_type,
278 const void *buffer,
279 size_t length)
280{
281 return mime_data_list_add(&source->mimes, mime_type, buffer, length);
282}
283
284SDL_bool
285Wayland_data_source_has_mime(SDL_WaylandDataSource *source,
286 const char *mime_type)
287{
288 SDL_bool found = SDL_FALSE;
289
290 if (source != NULL) {
291 found = mime_data_list_find(&source->mimes, mime_type) != NULL;
292 }
293 return found;
294}
295
296void*
297Wayland_data_source_get_data(SDL_WaylandDataSource *source,
298 size_t *length, const char* mime_type,
299 SDL_bool null_terminate)
300{
301 SDL_MimeDataList *mime_data = NULL;
302 void *buffer = NULL;
303 *length = 0;
304
305 if (source == NULL) {
306 SDL_SetError("Invalid data source");
307 } else {
308 mime_data = mime_data_list_find(&source->mimes, mime_type);
309 if (mime_data != NULL && mime_data->length > 0) {
310 buffer = SDL_malloc(mime_data->length);
311 if (buffer == NULL) {
312 *length = SDL_OutOfMemory();
313 } else {
314 *length = mime_data->length;
315 SDL_memcpy(buffer, mime_data->data, mime_data->length);
316 }
317 }
318 }
319
320 return buffer;
321}
322
323void
324Wayland_data_source_destroy(SDL_WaylandDataSource *source)
325{
326 if (source != NULL) {
327 wl_data_source_destroy(source->source);
328 mime_data_list_free(&source->mimes);
329 SDL_free(source);
330 }
331}
332
333void*
334Wayland_data_offer_receive(SDL_WaylandDataOffer *offer,
335 size_t *length, const char* mime_type,
336 SDL_bool null_terminate)
337{
338 SDL_WaylandDataDevice *data_device = NULL;
339
340 int pipefd[2];
341 void *buffer = NULL;
342 *length = 0;
343
344 if (offer == NULL) {
345 SDL_SetError("Invalid data offer");
346 } else if ((data_device = offer->data_device) == NULL) {
347 SDL_SetError("Data device not initialized");
348 } else if (pipe2(pipefd, O_CLOEXEC|O_NONBLOCK) == -1) {
349 SDL_SetError("Could not read pipe");
350 } else {
351 wl_data_offer_receive(offer->offer, mime_type, pipefd[1]);
352
353 /* TODO: Needs pump and flush? */
354 WAYLAND_wl_display_flush(data_device->video_data->display);
355
356 close(pipefd[1]);
357
358 while (read_pipe(pipefd[0], &buffer, length, null_terminate) > 0);
359 close(pipefd[0]);
360 }
361 return buffer;
362}
363
364int
365Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer,
366 const char* mime_type)
367{
368 return mime_data_list_add(&offer->mimes, mime_type, NULL, 0);
369}
370
371
372SDL_bool
373Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer,
374 const char *mime_type)
375{
376 SDL_bool found = SDL_FALSE;
377
378 if (offer != NULL) {
379 found = mime_data_list_find(&offer->mimes, mime_type) != NULL;
380 }
381 return found;
382}
383
384void
385Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer)
386{
387 if (offer != NULL) {
388 wl_data_offer_destroy(offer->offer);
389 mime_data_list_free(&offer->mimes);
390 SDL_free(offer);
391 }
392}
393
394int
395Wayland_data_device_clear_selection(SDL_WaylandDataDevice *data_device)
396{
397 int status = 0;
398
399 if (data_device == NULL || data_device->data_device == NULL) {
400 status = SDL_SetError("Invalid Data Device");
401 } else if (data_device->selection_source != 0) {
402 wl_data_device_set_selection(data_device->data_device, NULL, 0);
403 data_device->selection_source = NULL;
404 }
405 return status;
406}
407
408int
409Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
410 SDL_WaylandDataSource *source)
411{
412 int status = 0;
413 size_t num_offers = 0;
414 size_t index = 0;
415
416 if (data_device == NULL) {
417 status = SDL_SetError("Invalid Data Device");
418 } else if (source == NULL) {
419 status = SDL_SetError("Invalid source");
420 } else {
421 SDL_MimeDataList *mime_data = NULL;
422
423 wl_list_for_each(mime_data, &(source->mimes), link) {
424 wl_data_source_offer(source->source,
425 mime_data->mime_type);
426
427 /* TODO - Improve system for multiple mime types to same data */
428 for (index = 0; index < MIME_LIST_SIZE; ++index) {
429 if (strcmp(mime_conversion_list[index][1], mime_data->mime_type) == 0) {
430 wl_data_source_offer(source->source,
431 mime_conversion_list[index][0]);
432 }
433 }
434 /* */
435
436 ++num_offers;
437 }
438
439 if (num_offers == 0) {
440 Wayland_data_device_clear_selection(data_device);
441 status = SDL_SetError("No mime data");
442 } else {
443 /* Only set if there is a valid serial if not set it later */
444 if (data_device->selection_serial != 0) {
445 wl_data_device_set_selection(data_device->data_device,
446 source->source,
447 data_device->selection_serial);
448 }
449 data_device->selection_source = source;
450 }
451 }
452
453 return status;
454}
455
456int
457Wayland_data_device_set_serial(SDL_WaylandDataDevice *data_device,
458 uint32_t serial)
459{
460 int status = -1;
461 if (data_device != NULL) {
462 status = 0;
463
464 /* If there was no serial and there is a pending selection set it now. */
465 if (data_device->selection_serial == 0
466 && data_device->selection_source != NULL) {
467 wl_data_device_set_selection(data_device->data_device,
468 data_device->selection_source->source,
469 serial);
470 }
471
472 data_device->selection_serial = serial;
473 }
474
475 return status;
476}
477
478#endif /* SDL_VIDEO_DRIVER_WAYLAND */
479
480/* vi: set ts=4 sw=4 expandtab: */
481