| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| |
| #include "../../SDL_internal.h" |
| |
| #if SDL_VIDEO_DRIVER_WAYLAND |
| |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <limits.h> |
| #include <signal.h> |
| |
| #include "SDL_stdinc.h" |
| #include "../../core/unix/SDL_poll.h" |
| |
| #include "SDL_waylandvideo.h" |
| #include "SDL_waylanddatamanager.h" |
| |
| #include "SDL_waylanddyn.h" |
| |
| static ssize_t |
| write_pipe(int fd, const void* buffer, size_t total_length, size_t *pos) |
| { |
| int ready = 0; |
| ssize_t bytes_written = 0; |
| ssize_t length = total_length - *pos; |
| |
| sigset_t sig_set; |
| sigset_t old_sig_set; |
| struct timespec zerotime = {0}; |
| |
| ready = SDL_IOReady(fd, SDL_TRUE, 1 * 1000); |
| |
| sigemptyset(&sig_set); |
| sigaddset(&sig_set, SIGPIPE); |
| |
| #if SDL_THREADS_DISABLED |
| sigprocmask(SIG_BLOCK, &sig_set, &old_sig_set); |
| #else |
| pthread_sigmask(SIG_BLOCK, &sig_set, &old_sig_set); |
| #endif |
| |
| if (ready == 0) { |
| bytes_written = SDL_SetError("Pipe timeout"); |
| } else if (ready < 0) { |
| bytes_written = SDL_SetError("Pipe select error"); |
| } else { |
| if (length > 0) { |
| bytes_written = write(fd, (Uint8*)buffer + *pos, SDL_min(length, PIPE_BUF)); |
| } |
| |
| if (bytes_written > 0) { |
| *pos += bytes_written; |
| } |
| } |
| |
| sigtimedwait(&sig_set, 0, &zerotime); |
| |
| #if SDL_THREADS_DISABLED |
| sigprocmask(SIG_SETMASK, &old_sig_set, NULL); |
| #else |
| pthread_sigmask(SIG_SETMASK, &old_sig_set, NULL); |
| #endif |
| |
| return bytes_written; |
| } |
| |
| static ssize_t |
| read_pipe(int fd, void** buffer, size_t* total_length, SDL_bool null_terminate) |
| { |
| int ready = 0; |
| void* output_buffer = NULL; |
| char temp[PIPE_BUF]; |
| size_t new_buffer_length = 0; |
| ssize_t bytes_read = 0; |
| size_t pos = 0; |
| |
| ready = SDL_IOReady(fd, SDL_FALSE, 1 * 1000); |
| |
| if (ready == 0) { |
| bytes_read = SDL_SetError("Pipe timeout"); |
| } else if (ready < 0) { |
| bytes_read = SDL_SetError("Pipe select error"); |
| } else { |
| bytes_read = read(fd, temp, sizeof(temp)); |
| } |
| |
| if (bytes_read > 0) { |
| pos = *total_length; |
| *total_length += bytes_read; |
| |
| if (null_terminate == SDL_TRUE) { |
| new_buffer_length = *total_length + 1; |
| } else { |
| new_buffer_length = *total_length; |
| } |
| |
| if (*buffer == NULL) { |
| output_buffer = SDL_malloc(new_buffer_length); |
| } else { |
| output_buffer = SDL_realloc(*buffer, new_buffer_length); |
| } |
| |
| if (output_buffer == NULL) { |
| bytes_read = SDL_OutOfMemory(); |
| } else { |
| SDL_memcpy((Uint8*)output_buffer + pos, temp, bytes_read); |
| |
| if (null_terminate == SDL_TRUE) { |
| SDL_memset((Uint8*)output_buffer + (new_buffer_length - 1), 0, 1); |
| } |
| |
| *buffer = output_buffer; |
| } |
| } |
| |
| return bytes_read; |
| } |
| |
| #define MIME_LIST_SIZE 4 |
| |
| static const char* mime_conversion_list[MIME_LIST_SIZE][2] = { |
| {"text/plain", TEXT_MIME}, |
| {"TEXT", TEXT_MIME}, |
| {"UTF8_STRING", TEXT_MIME}, |
| {"STRING", TEXT_MIME} |
| }; |
| |
| const char* |
| Wayland_convert_mime_type(const char *mime_type) |
| { |
| const char *found = mime_type; |
| |
| size_t index = 0; |
| |
| for (index = 0; index < MIME_LIST_SIZE; ++index) { |
| if (strcmp(mime_conversion_list[index][0], mime_type) == 0) { |
| found = mime_conversion_list[index][1]; |
| break; |
| } |
| } |
| |
| return found; |
| } |
| |
| static SDL_MimeDataList* |
| mime_data_list_find(struct wl_list* list, |
| const char* mime_type) |
| { |
| SDL_MimeDataList *found = NULL; |
| |
| SDL_MimeDataList *mime_list = NULL; |
| wl_list_for_each(mime_list, list, link) { |
| if (strcmp(mime_list->mime_type, mime_type) == 0) { |
| found = mime_list; |
| break; |
| } |
| } |
| return found; |
| } |
| |
| static int |
| mime_data_list_add(struct wl_list* list, |
| const char* mime_type, |
| const void* buffer, size_t length) |
| { |
| int status = 0; |
| size_t mime_type_length = 0; |
| SDL_MimeDataList *mime_data = NULL; |
| void *internal_buffer = NULL; |
| |
| if (buffer != NULL) { |
| internal_buffer = SDL_malloc(length); |
| if (internal_buffer == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| SDL_memcpy(internal_buffer, buffer, length); |
| } |
| |
| mime_data = mime_data_list_find(list, mime_type); |
| |
| if (mime_data == NULL) { |
| mime_data = SDL_calloc(1, sizeof(*mime_data)); |
| if (mime_data == NULL) { |
| status = SDL_OutOfMemory(); |
| } else { |
| WAYLAND_wl_list_insert(list, &(mime_data->link)); |
| |
| mime_type_length = strlen(mime_type) + 1; |
| mime_data->mime_type = SDL_malloc(mime_type_length); |
| if (mime_data->mime_type == NULL) { |
| status = SDL_OutOfMemory(); |
| } else { |
| SDL_memcpy(mime_data->mime_type, mime_type, mime_type_length); |
| } |
| } |
| } |
| |
| if (mime_data != NULL && buffer != NULL && length > 0) { |
| if (mime_data->data != NULL) { |
| SDL_free(mime_data->data); |
| } |
| mime_data->data = internal_buffer; |
| mime_data->length = length; |
| } else { |
| SDL_free(internal_buffer); |
| } |
| |
| return status; |
| } |
| |
| static void |
| mime_data_list_free(struct wl_list *list) |
| { |
| SDL_MimeDataList *mime_data = NULL; |
| SDL_MimeDataList *next = NULL; |
| |
| wl_list_for_each_safe(mime_data, next, list, link) { |
| if (mime_data->data != NULL) { |
| SDL_free(mime_data->data); |
| } |
| if (mime_data->mime_type != NULL) { |
| SDL_free(mime_data->mime_type); |
| } |
| SDL_free(mime_data); |
| } |
| } |
| |
| ssize_t |
| Wayland_data_source_send(SDL_WaylandDataSource *source, |
| const char *mime_type, int fd) |
| { |
| size_t written_bytes = 0; |
| ssize_t status = 0; |
| SDL_MimeDataList *mime_data = NULL; |
| |
| mime_type = Wayland_convert_mime_type(mime_type); |
| mime_data = mime_data_list_find(&source->mimes, |
| mime_type); |
| |
| if (mime_data == NULL || mime_data->data == NULL) { |
| status = SDL_SetError("Invalid mime type"); |
| close(fd); |
| } else { |
| while (write_pipe(fd, mime_data->data, mime_data->length, |
| &written_bytes) > 0); |
| close(fd); |
| status = written_bytes; |
| } |
| return status; |
| } |
| |
| int Wayland_data_source_add_data(SDL_WaylandDataSource *source, |
| const char *mime_type, |
| const void *buffer, |
| size_t length) |
| { |
| return mime_data_list_add(&source->mimes, mime_type, buffer, length); |
| } |
| |
| SDL_bool |
| Wayland_data_source_has_mime(SDL_WaylandDataSource *source, |
| const char *mime_type) |
| { |
| SDL_bool found = SDL_FALSE; |
| |
| if (source != NULL) { |
| found = mime_data_list_find(&source->mimes, mime_type) != NULL; |
| } |
| return found; |
| } |
| |
| void* |
| Wayland_data_source_get_data(SDL_WaylandDataSource *source, |
| size_t *length, const char* mime_type, |
| SDL_bool null_terminate) |
| { |
| SDL_MimeDataList *mime_data = NULL; |
| void *buffer = NULL; |
| *length = 0; |
| |
| if (source == NULL) { |
| SDL_SetError("Invalid data source"); |
| } else { |
| mime_data = mime_data_list_find(&source->mimes, mime_type); |
| if (mime_data != NULL && mime_data->length > 0) { |
| buffer = SDL_malloc(mime_data->length); |
| if (buffer == NULL) { |
| *length = SDL_OutOfMemory(); |
| } else { |
| *length = mime_data->length; |
| SDL_memcpy(buffer, mime_data->data, mime_data->length); |
| } |
| } |
| } |
| |
| return buffer; |
| } |
| |
| void |
| Wayland_data_source_destroy(SDL_WaylandDataSource *source) |
| { |
| if (source != NULL) { |
| wl_data_source_destroy(source->source); |
| mime_data_list_free(&source->mimes); |
| SDL_free(source); |
| } |
| } |
| |
| void* |
| Wayland_data_offer_receive(SDL_WaylandDataOffer *offer, |
| size_t *length, const char* mime_type, |
| SDL_bool null_terminate) |
| { |
| SDL_WaylandDataDevice *data_device = NULL; |
| |
| int pipefd[2]; |
| void *buffer = NULL; |
| *length = 0; |
| |
| if (offer == NULL) { |
| SDL_SetError("Invalid data offer"); |
| } else if ((data_device = offer->data_device) == NULL) { |
| SDL_SetError("Data device not initialized"); |
| } else if (pipe2(pipefd, O_CLOEXEC|O_NONBLOCK) == -1) { |
| SDL_SetError("Could not read pipe"); |
| } else { |
| wl_data_offer_receive(offer->offer, mime_type, pipefd[1]); |
| |
| /* TODO: Needs pump and flush? */ |
| WAYLAND_wl_display_flush(data_device->video_data->display); |
| |
| close(pipefd[1]); |
| |
| while (read_pipe(pipefd[0], &buffer, length, null_terminate) > 0); |
| close(pipefd[0]); |
| } |
| return buffer; |
| } |
| |
| int |
| Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer, |
| const char* mime_type) |
| { |
| return mime_data_list_add(&offer->mimes, mime_type, NULL, 0); |
| } |
| |
| |
| SDL_bool |
| Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, |
| const char *mime_type) |
| { |
| SDL_bool found = SDL_FALSE; |
| |
| if (offer != NULL) { |
| found = mime_data_list_find(&offer->mimes, mime_type) != NULL; |
| } |
| return found; |
| } |
| |
| void |
| Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer) |
| { |
| if (offer != NULL) { |
| wl_data_offer_destroy(offer->offer); |
| mime_data_list_free(&offer->mimes); |
| SDL_free(offer); |
| } |
| } |
| |
| int |
| Wayland_data_device_clear_selection(SDL_WaylandDataDevice *data_device) |
| { |
| int status = 0; |
| |
| if (data_device == NULL || data_device->data_device == NULL) { |
| status = SDL_SetError("Invalid Data Device"); |
| } else if (data_device->selection_source != 0) { |
| wl_data_device_set_selection(data_device->data_device, NULL, 0); |
| data_device->selection_source = NULL; |
| } |
| return status; |
| } |
| |
| int |
| Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device, |
| SDL_WaylandDataSource *source) |
| { |
| int status = 0; |
| size_t num_offers = 0; |
| size_t index = 0; |
| |
| if (data_device == NULL) { |
| status = SDL_SetError("Invalid Data Device"); |
| } else if (source == NULL) { |
| status = SDL_SetError("Invalid source"); |
| } else { |
| SDL_MimeDataList *mime_data = NULL; |
| |
| wl_list_for_each(mime_data, &(source->mimes), link) { |
| wl_data_source_offer(source->source, |
| mime_data->mime_type); |
| |
| /* TODO - Improve system for multiple mime types to same data */ |
| for (index = 0; index < MIME_LIST_SIZE; ++index) { |
| if (strcmp(mime_conversion_list[index][1], mime_data->mime_type) == 0) { |
| wl_data_source_offer(source->source, |
| mime_conversion_list[index][0]); |
| } |
| } |
| /* */ |
| |
| ++num_offers; |
| } |
| |
| if (num_offers == 0) { |
| Wayland_data_device_clear_selection(data_device); |
| status = SDL_SetError("No mime data"); |
| } else { |
| /* Only set if there is a valid serial if not set it later */ |
| if (data_device->selection_serial != 0) { |
| wl_data_device_set_selection(data_device->data_device, |
| source->source, |
| data_device->selection_serial); |
| } |
| data_device->selection_source = source; |
| } |
| } |
| |
| return status; |
| } |
| |
| int |
| Wayland_data_device_set_serial(SDL_WaylandDataDevice *data_device, |
| uint32_t serial) |
| { |
| int status = -1; |
| if (data_device != NULL) { |
| status = 0; |
| |
| /* If there was no serial and there is a pending selection set it now. */ |
| if (data_device->selection_serial == 0 |
| && data_device->selection_source != NULL) { |
| wl_data_device_set_selection(data_device->data_device, |
| data_device->selection_source->source, |
| serial); |
| } |
| |
| data_device->selection_serial = serial; |
| } |
| |
| return status; |
| } |
| |
| #endif /* SDL_VIDEO_DRIVER_WAYLAND */ |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |