diff --git a/include/SDL3/SDL_clipboard.h b/include/SDL3/SDL_clipboard.h index 06b3ac84e..a17a24fb7 100644 --- a/include/SDL3/SDL_clipboard.h +++ b/include/SDL3/SDL_clipboard.h @@ -129,6 +129,96 @@ extern DECLSPEC char * SDLCALL SDL_GetPrimarySelectionText(void); */ extern DECLSPEC SDL_bool SDLCALL SDL_HasPrimarySelectionText(void); +/** + * Callback function that will be called when data for the specified mime-type + * is requested by the OS. + * + * \param size The length of the returned data + * \param mime_type The requested mime-type + * \param userdata A pointer to provided user data + * \returns a pointer to the data for the provided mime-type. Returning NULL or + * setting length to 0 will cause no data to be sent to the "receiver". It is + * up to the receiver to handle this. Essentially returning no data is more or + * less undefined behavior and may cause breakage in receiving applications. + * The returned data will not be freed so it needs to be retained and dealt + * with internally. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetClipboardData + */ +typedef void *(SDLCALL *SDL_ClipboardDataCallback)(size_t *size, const char *mime_type, void *userdata); + +/** + * \brief Offer clipboard data to the OS + * + * Tell the operating system that the application is offering clipboard data + * for each of the proivded mime-types. Once another application requests the + * data the callback function will be called allowing it to generate and + * respond with the data for the requested mime-type. + * + * The userdata submitted to this function needs to be freed manually. The + * following scenarios need to be handled: + * + * - When the programs clipboard is replaced (cancelled) SDL_EVENT_CLIPBOARD_CANCELLED + * - Before calling SDL_Quit() + * + * \param callback A function pointer to the function that provides the clipboard data + * \param mime_count The number of mime-types in the mime_types list + * \param mime_types A list of mime-types that are being offered + * \param userdata An opaque pointer that will be forwarded to the callback + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_ClipboardDataCallback + * \sa SDL_GetClipboardUserdata + * \sa SDL_SetClipboardData + * \sa SDL_GetClipboardData + * \sa SDL_HasClipboardData + */ +extern DECLSPEC int SDLCALL SDL_SetClipboardData(SDL_ClipboardDataCallback callback, size_t mime_count, + const char **mime_types, void *userdata); + +/** + * Retrieve previously set userdata if any. + * + * \since This function is available since SDL 3.0.0. + * + * \returns a pointer to the data or NULL if no data exists + */ +extern DECLSPEC void *SDLCALL SDL_GetClipboardUserdata(void); + +/** + * Get the data from clipboard for a given mime type + * + * \param[out] length A pointer to hold the buffer length + * \param mime_type The mime type to read from the clipboard + * \returns the retrieved data buffer or NULL on failure; call + * SDL_GetError() for more information. Caller must call + * SDL_free() on the returned pointer when done with it. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetClipboardData + */ +extern DECLSPEC void *SDLCALL SDL_GetClipboardData(size_t *length, const char *mime_type); + +/** + * Query whether there is data in the clipboard for the provided mime type + * + * \param mime_type The mime type to check for data for + * + * \returns SDL_TRUE if there exists data in clipboard for the provided mime + * type, SDL_FALSE if it does not. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetClipboardData + * \sa SDL_GetClipboardData + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasClipboardData(const char *mime_type); /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index a28cced2d..a92a2fe4b 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -172,6 +172,7 @@ typedef enum /* Clipboard events */ SDL_EVENT_CLIPBOARD_UPDATE = 0x900, /**< The clipboard or primary selection changed */ + SDL_EVENT_CLIPBOARD_CANCELLED, /**< The clipboard or primary selection cancelled */ /* Drag and drop events */ SDL_EVENT_DROP_FILE = 0x1000, /**< The system requests a file open */ @@ -530,6 +531,18 @@ typedef struct SDL_DropEvent float y; /**< Y coordinate, relative to window (not on begin) */ } SDL_DropEvent; +/** + * \brief An event triggered when the applications active clipboard content is cancelled by new clipboard content + * \note Primary use for this event is to free any userdata you may have provided when setting the clipboard data. + * + * \sa SDL_SetClipboardData + */ +typedef struct SDL_ClipboardCancelled +{ + Uint32 type; /**< ::SDL_EVENT_CLIPBOARD_CANCELLED or ::SDL_EVENT_CLIPBOARD_UPDATE */ + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + void *userdata; /**< User data if any has been set. NULL for ::SDL_EVENT_CLIPBOARD_UPDATE */ +} SDL_ClipboardEvent; /** * \brief Sensor event structure (event.sensor.*) @@ -624,6 +637,7 @@ typedef union SDL_Event SDL_SysWMEvent syswm; /**< System dependent window event data */ SDL_TouchFingerEvent tfinger; /**< Touch finger event data */ SDL_DropEvent drop; /**< Drag and drop event data */ + SDL_ClipboardEvent clipboard; /**< Clipboard cancelled event data */ /* This is necessary for ABI compatibility between Visual C++ and GCC. Visual C++ will respect the push pack pragma and use 52 bytes (size of diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 3927b2623..78075cd63 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -842,6 +842,10 @@ SDL3_0.0.0 { SDL_CreatePopupWindow; SDL_GetWindowParent; SDL_CreateWindowWithPosition; + SDL_SetClipboardData; + SDL_GetClipboardUserdata; + SDL_GetClipboardData; + SDL_HasClipboardData; SDL_GetAudioStreamFormat; SDL_SetAudioStreamFormat; SDL_CreateRWLock; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index d96acd615..c5f65c31f 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -868,6 +868,10 @@ #define SDL_CreatePopupWindow SDL_CreatePopupWindow_REAL #define SDL_GetWindowParent SDL_GetWindowParent_REAL #define SDL_CreateWindowWithPosition SDL_CreateWindowWithPosition_REAL +#define SDL_SetClipboardData SDL_SetClipboardData_REAL +#define SDL_GetClipboardUserdata SDL_GetClipboardUserdata_REAL +#define SDL_GetClipboardData SDL_GetClipboardData_REAL +#define SDL_HasClipboardData SDL_HasClipboardData_REAL #define SDL_GetAudioStreamFormat SDL_GetAudioStreamFormat_REAL #define SDL_SetAudioStreamFormat SDL_SetAudioStreamFormat_REAL #define SDL_CreateRWLock SDL_CreateRWLock_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 9ebfecd4e..5bfb06aad 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -913,6 +913,10 @@ SDL_DYNAPI_PROC(SDL_SystemTheme,SDL_GetSystemTheme,(void),(),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_CreatePopupWindow,(SDL_Window *a, int b, int c, int d, int e, Uint32 f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_GetWindowParent,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindowWithPosition,(const char *a, int b, int c, int d, int e, Uint32 f),(a,b,c,d,e,f),return) +SDL_DYNAPI_PROC(int,SDL_SetClipboardData,(SDL_ClipboardDataCallback a, size_t b, const char **c, void *d),(a,b,c,d),return) +SDL_DYNAPI_PROC(void*,SDL_GetClipboardUserdata,(void),(),return) +SDL_DYNAPI_PROC(void*,SDL_GetClipboardData,(size_t *a, const char *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_HasClipboardData,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat *b, int *c, int *d, SDL_AudioFormat *e, int *f, int *g),(a,b,c,d,e,f,g),return) SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat b, int c, int d, SDL_AudioFormat e, int f, int g),(a,b,c,d,e,f,g),return) SDL_DYNAPI_PROC(SDL_RWLock*,SDL_CreateRWLock,(void),(),return) diff --git a/src/events/SDL_clipboardevents.c b/src/events/SDL_clipboardevents.c index 961f4b2f8..ae6519d16 100644 --- a/src/events/SDL_clipboardevents.c +++ b/src/events/SDL_clipboardevents.c @@ -34,7 +34,24 @@ int SDL_SendClipboardUpdate(void) if (SDL_EventEnabled(SDL_EVENT_CLIPBOARD_UPDATE)) { SDL_Event event; event.type = SDL_EVENT_CLIPBOARD_UPDATE; - event.common.timestamp = 0; + event.clipboard.timestamp = 0; + event.clipboard.userdata = NULL; + posted = (SDL_PushEvent(&event) > 0); + } + return posted; +} + +int SDL_SendClipboardCancelled(void *userdata) +{ + int posted; + + /* Post the event, if desired */ + posted = 0; + if (SDL_EventEnabled(SDL_EVENT_CLIPBOARD_CANCELLED)) { + SDL_Event event; + event.type = SDL_EVENT_CLIPBOARD_CANCELLED; + event.clipboard.timestamp = 0; + event.clipboard.userdata = userdata; posted = (SDL_PushEvent(&event) > 0); } return posted; diff --git a/src/events/SDL_clipboardevents_c.h b/src/events/SDL_clipboardevents_c.h index ce74e3cbd..265eb3893 100644 --- a/src/events/SDL_clipboardevents_c.h +++ b/src/events/SDL_clipboardevents_c.h @@ -25,4 +25,6 @@ extern int SDL_SendClipboardUpdate(void); +extern int SDL_SendClipboardCancelled(void *userdata); + #endif /* SDL_clipboardevents_c_h_ */ diff --git a/src/video/SDL_clipboard.c b/src/video/SDL_clipboard.c index ae4163290..fdcbf19b3 100644 --- a/src/video/SDL_clipboard.c +++ b/src/video/SDL_clipboard.c @@ -22,6 +22,22 @@ #include "SDL_sysvideo.h" +int +SDL_SetClipboardData(SDL_ClipboardDataCallback callback, size_t mime_count, const char **mime_types, void *userdata) +{ + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + + if (_this == NULL) { + return SDL_SetError("Video subsystem must be initialized to set clipboard text"); + } + + if (_this->SetClipboardData) { + return _this->SetClipboardData(_this, callback, mime_count, mime_types, userdata); + } else { + return SDL_Unsupported(); + } +} + int SDL_SetClipboardText(const char *text) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); @@ -62,6 +78,22 @@ int SDL_SetPrimarySelectionText(const char *text) } } +void * +SDL_GetClipboardData(size_t *length, const char *mime_type) +{ + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + if (_this == NULL) { + SDL_SetError("Video subsystem must be initialized to get clipboard data"); + return NULL; + } + + if (_this->GetClipboardData) { + return _this->GetClipboardData(_this, length, mime_type); + } else { + return NULL; + } +} + char * SDL_GetClipboardText(void) { @@ -104,6 +136,21 @@ SDL_GetPrimarySelectionText(void) } } +SDL_bool +SDL_HasClipboardData(const char *mime_type) +{ + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + if (_this == NULL) { + SDL_SetError("Video subsystem must be initialized to check clipboard data"); + return SDL_FALSE; + } + + if (_this->HasClipboardData) { + return _this->HasClipboardData(_this, mime_type); + } + return SDL_FALSE; +} + SDL_bool SDL_HasClipboardText(void) { @@ -145,3 +192,19 @@ SDL_HasPrimarySelectionText(void) return SDL_FALSE; } + +void * +SDL_GetClipboardUserdata(void) +{ + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + + if (_this == NULL) { + SDL_SetError("Video subsystem must be initialized to check clipboard userdata"); + return NULL; + } + + if (_this->GetClipboardUserdata) { + return _this->GetClipboardUserdata(_this); + } + return NULL; +} diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 2e87dab57..3321db9d9 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -334,6 +334,11 @@ struct SDL_VideoDevice int (*SetPrimarySelectionText)(SDL_VideoDevice *_this, const char *text); char *(*GetPrimarySelectionText)(SDL_VideoDevice *_this); SDL_bool (*HasPrimarySelectionText)(SDL_VideoDevice *_this); + int (*SetClipboardData)(SDL_VideoDevice *_this, SDL_ClipboardDataCallback callback, size_t mime_count, + const char **mime_types, void *userdata); + void *(*GetClipboardData)(SDL_VideoDevice *_this, size_t *len, const char *mime_type); + SDL_bool (*HasClipboardData)(SDL_VideoDevice *_this, const char *mime_type); + void *(*GetClipboardUserdata)(SDL_VideoDevice *_this); /* MessageBox */ int (*ShowMessageBox)(SDL_VideoDevice *_this, const SDL_MessageBoxData *messageboxdata, int *buttonid); diff --git a/src/video/wayland/SDL_waylandclipboard.c b/src/video/wayland/SDL_waylandclipboard.c index dd9572f01..dd962a801 100644 --- a/src/video/wayland/SDL_waylandclipboard.c +++ b/src/video/wayland/SDL_waylandclipboard.c @@ -22,10 +22,74 @@ #ifdef SDL_VIDEO_DRIVER_WAYLAND +#include "../../events/SDL_events_c.h" #include "SDL_waylanddatamanager.h" #include "SDL_waylandevents_c.h" #include "SDL_waylandclipboard.h" +int Wayland_SetClipboardData(SDL_VideoDevice *_this, SDL_ClipboardDataCallback callback, size_t mime_count, const char **mime_types, + void *userdata) +{ + SDL_VideoData *video_data = NULL; + SDL_WaylandDataDevice *data_device = NULL; + + int status = 0; + + video_data = _this->driverdata; + if (video_data->input != NULL && video_data->input->data_device != NULL) { + data_device = video_data->input->data_device; + + if (callback && mime_types) { + SDL_WaylandDataSource *source = Wayland_data_source_create(_this); + Wayland_data_source_set_callback(source, callback, userdata, SDL_FALSE); + + status = Wayland_data_device_set_selection(data_device, source, mime_count, mime_types); + if (status != 0) { + Wayland_data_source_destroy(source); + } + } else { + status = Wayland_data_device_clear_selection(data_device); + } + } + + return status; +} + +#define TEXT_MIME_TYPES_LEN 5 +static const char *text_mime_types[TEXT_MIME_TYPES_LEN] = { + TEXT_MIME, + "text/plain", + "TEXT", + "UTF8_STRING", + "STRING", +}; + +static void *Wayland_ClipboardTextCallback(size_t *length, const char *mime_type, void *userdata) +{ + void *data = NULL; + SDL_bool valid_mime_type = SDL_FALSE; + *length = 0; + + if (userdata == NULL) { + return data; + } + + for (size_t i = 0; i < TEXT_MIME_TYPES_LEN; ++i) { + if (SDL_strcmp(mime_type, text_mime_types[i]) == 0) { + valid_mime_type = SDL_TRUE; + break; + } + } + + if (valid_mime_type) { + char *text = userdata; + *length = SDL_strlen(text); + data = userdata; + } + + return data; +} + int Wayland_SetClipboardText(SDL_VideoDevice *_this, const char *text) { SDL_VideoData *video_data = NULL; @@ -39,12 +103,12 @@ int Wayland_SetClipboardText(SDL_VideoDevice *_this, const char *text) video_data = _this->driverdata; if (video_data->input != NULL && video_data->input->data_device != NULL) { data_device = video_data->input->data_device; + if (text[0] != '\0') { SDL_WaylandDataSource *source = Wayland_data_source_create(_this); - Wayland_data_source_add_data(source, TEXT_MIME, text, - SDL_strlen(text)); + Wayland_data_source_set_callback(source, Wayland_ClipboardTextCallback, SDL_strdup(text), SDL_TRUE); - status = Wayland_data_device_set_selection(data_device, source); + status = Wayland_data_device_set_selection(data_device, source, TEXT_MIME_TYPES_LEN, text_mime_types); if (status != 0) { Wayland_data_source_destroy(source); } @@ -72,11 +136,12 @@ int Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text) primary_selection_device = video_data->input->primary_selection_device; if (text[0] != '\0') { SDL_WaylandPrimarySelectionSource *source = Wayland_primary_selection_source_create(_this); - Wayland_primary_selection_source_add_data(source, TEXT_MIME, text, - SDL_strlen(text)); + Wayland_primary_selection_source_set_callback(source, Wayland_ClipboardTextCallback, SDL_strdup(text)); status = Wayland_primary_selection_device_set_selection(primary_selection_device, - source); + source, + TEXT_MIME_TYPES_LEN, + text_mime_types); if (status != 0) { Wayland_primary_selection_source_destroy(source); } @@ -89,6 +154,29 @@ int Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text) return status; } +void *Wayland_GetClipboardData(SDL_VideoDevice *_this, size_t *length, const char *mime_type) +{ + SDL_VideoData *video_data = NULL; + SDL_WaylandDataDevice *data_device = NULL; + + void *buffer = NULL; + + video_data = _this->driverdata; + if (video_data->input != NULL && video_data->input->data_device != NULL) { + data_device = video_data->input->data_device; + if (data_device->selection_source != NULL) { + buffer = Wayland_data_source_get_data(data_device->selection_source, + length, mime_type, SDL_FALSE); + } else if (Wayland_data_offer_has_mime( + data_device->selection_offer, mime_type)) { + buffer = Wayland_data_offer_receive(data_device->selection_offer, + length, mime_type, SDL_FALSE); + } + } + + return buffer; +} + char *Wayland_GetClipboardText(SDL_VideoDevice *_this) { SDL_VideoData *video_data = NULL; @@ -103,13 +191,10 @@ char *Wayland_GetClipboardText(SDL_VideoDevice *_this) video_data = _this->driverdata; if (video_data->input != NULL && video_data->input->data_device != NULL) { data_device = video_data->input->data_device; - /* Prefer own selection, if not canceled */ - if (Wayland_data_source_has_mime( - data_device->selection_source, TEXT_MIME)) { - text = Wayland_data_source_get_data(data_device->selection_source, - &length, TEXT_MIME, SDL_TRUE); + if (data_device->selection_source != NULL) { + text = Wayland_data_source_get_data(data_device->selection_source, &length, TEXT_MIME, SDL_TRUE); } else if (Wayland_data_offer_has_mime( - data_device->selection_offer, TEXT_MIME)) { + data_device->selection_offer, TEXT_MIME)) { text = Wayland_data_offer_receive(data_device->selection_offer, &length, TEXT_MIME, SDL_TRUE); } @@ -137,13 +222,10 @@ char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this) video_data = _this->driverdata; if (video_data->input != NULL && video_data->input->primary_selection_device != NULL) { primary_selection_device = video_data->input->primary_selection_device; - /* Prefer own selection, if not canceled */ - if (Wayland_primary_selection_source_has_mime( - primary_selection_device->selection_source, TEXT_MIME)) { - text = Wayland_primary_selection_source_get_data(primary_selection_device->selection_source, - &length, TEXT_MIME, SDL_TRUE); + if (primary_selection_device->selection_source != NULL) { + text = Wayland_primary_selection_source_get_data(primary_selection_device->selection_source, &length, TEXT_MIME, SDL_TRUE); } else if (Wayland_primary_selection_offer_has_mime( - primary_selection_device->selection_offer, TEXT_MIME)) { + primary_selection_device->selection_offer, TEXT_MIME)) { text = Wayland_primary_selection_offer_receive(primary_selection_device->selection_offer, &length, TEXT_MIME, SDL_TRUE); } @@ -157,7 +239,7 @@ char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this) return text; } -SDL_bool Wayland_HasClipboardText(SDL_VideoDevice *_this) +static SDL_bool HasClipboardData(SDL_VideoDevice *_this, const char *mime_type) { SDL_VideoData *video_data = NULL; SDL_WaylandDataDevice *data_device = NULL; @@ -170,13 +252,22 @@ SDL_bool Wayland_HasClipboardText(SDL_VideoDevice *_this) if (video_data->input != NULL && video_data->input->data_device != NULL) { data_device = video_data->input->data_device; result = result || - Wayland_data_source_has_mime(data_device->selection_source, TEXT_MIME) || - Wayland_data_offer_has_mime(data_device->selection_offer, TEXT_MIME); + Wayland_data_offer_has_mime(data_device->selection_offer, mime_type); } } return result; } +SDL_bool Wayland_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type) +{ + return HasClipboardData(_this, mime_type); +} + +SDL_bool Wayland_HasClipboardText(SDL_VideoDevice *_this) +{ + return HasClipboardData(_this, TEXT_MIME); +} + SDL_bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this) { SDL_VideoData *video_data = NULL; @@ -190,8 +281,6 @@ SDL_bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this) if (video_data->input != NULL && video_data->input->primary_selection_device != NULL) { primary_selection_device = video_data->input->primary_selection_device; result = result || - Wayland_primary_selection_source_has_mime( - primary_selection_device->selection_source, TEXT_MIME) || Wayland_primary_selection_offer_has_mime( primary_selection_device->selection_offer, TEXT_MIME); } @@ -199,4 +288,22 @@ SDL_bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this) return result; } +void *Wayland_GetClipboardUserdata(SDL_VideoDevice *_this) +{ + SDL_VideoData *video_data = NULL; + SDL_WaylandDataDevice *data_device = NULL; + void *data = NULL; + + video_data = _this->driverdata; + if (video_data->input != NULL && video_data->input->data_device != NULL) { + data_device = video_data->input->data_device; + if (data_device->selection_source != NULL + && data_device->selection_source->userdata.internal == SDL_FALSE) { + data = data_device->selection_source->userdata.data; + } + } + + return data; +} + #endif /* SDL_VIDEO_DRIVER_WAYLAND */ diff --git a/src/video/wayland/SDL_waylandclipboard.h b/src/video/wayland/SDL_waylandclipboard.h index ba79aeedf..9876aef32 100644 --- a/src/video/wayland/SDL_waylandclipboard.h +++ b/src/video/wayland/SDL_waylandclipboard.h @@ -23,11 +23,16 @@ #ifndef SDL_waylandclipboard_h_ #define SDL_waylandclipboard_h_ +extern int Wayland_SetClipboardData(SDL_VideoDevice *_this, SDL_ClipboardDataCallback callback, size_t mime_count, + const char **mime_types, void *userdata); +extern void *Wayland_GetClipboardData(SDL_VideoDevice *_this, size_t *length, const char *mime_type); +extern SDL_bool Wayland_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type); extern int Wayland_SetClipboardText(SDL_VideoDevice *_this, const char *text); extern char *Wayland_GetClipboardText(SDL_VideoDevice *_this); extern SDL_bool Wayland_HasClipboardText(SDL_VideoDevice *_this); extern int Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text); extern char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this); extern SDL_bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this); +extern void *Wayland_GetClipboardUserdata(SDL_VideoDevice *_this); #endif /* SDL_waylandclipboard_h_ */ diff --git a/src/video/wayland/SDL_waylanddatamanager.c b/src/video/wayland/SDL_waylanddatamanager.c index 1d9dcc94b..47d6a8bf7 100644 --- a/src/video/wayland/SDL_waylanddatamanager.c +++ b/src/video/wayland/SDL_waylanddatamanager.c @@ -29,6 +29,7 @@ #include #include "../../core/unix/SDL_poll.h" +#include "../../events/SDL_events_c.h" #include "SDL_waylandvideo.h" #include "SDL_waylanddatamanager.h" @@ -136,31 +137,6 @@ static ssize_t read_pipe(int fd, void **buffer, size_t *total_length, SDL_bool n 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 (SDL_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) { @@ -242,131 +218,112 @@ static void mime_data_list_free(struct wl_list *list) } } -static ssize_t -Wayland_source_send(SDL_MimeDataList *mime_data, const char *mime_type, int fd) +static size_t +Wayland_send_data(void *data, size_t length, int fd) { - size_t written_bytes = 0; - ssize_t status = 0; + size_t result = 0; - 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) { + if (length > 0 && data != NULL) { + while (write_pipe(fd, data, length, &result) > 0) { + /* Just keep spinning */ } - close(fd); - status = written_bytes; } - return status; + close(fd); + + return result; } ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source, const char *mime_type, int fd) { - SDL_MimeDataList *mime_data = NULL; + void *data = NULL; + size_t length = 0; - mime_type = Wayland_convert_mime_type(mime_type); - mime_data = mime_data_list_find(&source->mimes, - mime_type); + if (source->callback) { + data = source->callback(&length, mime_type, source->userdata.data); + } - return Wayland_source_send(mime_data, mime_type, fd); + return Wayland_send_data(data, length, fd); } ssize_t Wayland_primary_selection_source_send(SDL_WaylandPrimarySelectionSource *source, const char *mime_type, int fd) { - SDL_MimeDataList *mime_data = NULL; + void *data = NULL; + size_t length = 0; - mime_type = Wayland_convert_mime_type(mime_type); - mime_data = mime_data_list_find(&source->mimes, - mime_type); - - return Wayland_source_send(mime_data, mime_type, fd); -} - -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); -} - -int Wayland_primary_selection_source_add_data(SDL_WaylandPrimarySelectionSource *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; + if (source->callback) { + data = source->callback(&length, mime_type, source->userdata.data); } - return found; + + return Wayland_send_data(data, length, fd); } -SDL_bool Wayland_primary_selection_source_has_mime(SDL_WaylandPrimarySelectionSource *source, - const char *mime_type) +void Wayland_data_source_set_callback(SDL_WaylandDataSource *source, + SDL_ClipboardDataCallback callback, + void *userdata, + SDL_bool internal) { - SDL_bool found = SDL_FALSE; - if (source != NULL) { - found = mime_data_list_find(&source->mimes, mime_type) != NULL; + source->callback = callback; + source->userdata.internal = internal; + source->userdata.data = userdata; } - return found; } -static void *Wayland_source_get_data(SDL_MimeDataList *mime_data, - size_t *length, - SDL_bool null_terminate) +int Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSource *source, + SDL_ClipboardDataCallback callback, + void *userdata) { - void *buffer = NULL; - - if (mime_data != NULL && mime_data->length > 0) { - size_t buffer_length = mime_data->length; + if (source == NULL) { + return SDL_InvalidParamError("source"); + } + source->callback = callback; + source->userdata.internal = SDL_TRUE; + source->userdata.data = userdata; + return 0; +} +static void *Wayland_clone_data_buffer(void *buffer, size_t *len, SDL_bool null_terminate) +{ + void *clone = NULL; + if (*len > 0 && buffer != NULL) { if (null_terminate == SDL_TRUE) { - ++buffer_length; - } - buffer = SDL_malloc(buffer_length); - if (buffer == NULL) { - *length = SDL_OutOfMemory(); + clone = SDL_malloc((*len)+1); + if (clone == NULL) { + SDL_OutOfMemory(); + } else { + SDL_memcpy(clone, buffer, *len); + ((char *) clone)[*len] = '\0'; + *len += 1; + } } else { - *length = mime_data->length; - SDL_memcpy(buffer, mime_data->data, mime_data->length); - if (null_terminate) { - *((Uint8 *)buffer + mime_data->length) = 0; + clone = SDL_malloc(*len); + if (clone == NULL) { + SDL_OutOfMemory(); + } else { + SDL_memcpy(clone, buffer, *len); } } } - - return buffer; + return clone; } 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; + void *internal_buffer; *length = 0; if (source == NULL) { SDL_SetError("Invalid data source"); - } else { - mime_data = mime_data_list_find(&source->mimes, mime_type); - buffer = Wayland_source_get_data(mime_data, length, null_terminate); + } else if (source->callback != NULL) { + internal_buffer = source->callback(length, mime_type, source->userdata.data); + buffer = Wayland_clone_data_buffer(internal_buffer, length, null_terminate); } return buffer; @@ -376,15 +333,15 @@ void *Wayland_primary_selection_source_get_data(SDL_WaylandPrimarySelectionSourc size_t *length, const char *mime_type, SDL_bool null_terminate) { - SDL_MimeDataList *mime_data = NULL; void *buffer = NULL; + void *internal_buffer; *length = 0; if (source == NULL) { SDL_SetError("Invalid primary selection source"); - } else { - mime_data = mime_data_list_find(&source->mimes, mime_type); - buffer = Wayland_source_get_data(mime_data, length, null_terminate); + } else if (source->callback) { + internal_buffer = source->callback(length, mime_type, source->userdata.data); + buffer = Wayland_clone_data_buffer(internal_buffer, length, null_terminate); } return buffer; @@ -398,7 +355,11 @@ void Wayland_data_source_destroy(SDL_WaylandDataSource *source) data_device->selection_source = NULL; } wl_data_source_destroy(source->source); - mime_data_list_free(&source->mimes); + if (source->userdata.internal == SDL_TRUE) { + SDL_free(source->userdata.data); + } else { + SDL_SendClipboardCancelled(source->userdata.data); + } SDL_free(source); } } @@ -411,7 +372,9 @@ void Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource primary_selection_device->selection_source = NULL; } zwp_primary_selection_source_v1_destroy(source->source); - mime_data_list_free(&source->mimes); + if (source->userdata.internal == SDL_TRUE) { + SDL_free(source->userdata.data); + } SDL_free(source); } } @@ -566,36 +529,27 @@ int Wayland_primary_selection_device_clear_selection(SDL_WaylandPrimarySelection } int Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device, - SDL_WaylandDataSource *source) + SDL_WaylandDataSource *source, + size_t mime_count, + const char **mime_types) { 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; + size_t index = 0; + const char *mime_type; - wl_list_for_each (mime_data, &(source->mimes), link) { + for (index = 0; index < mime_count; ++index) { + mime_type = mime_types[index]; 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 (SDL_strcmp(mime_conversion_list[index][1], mime_data->mime_type) == 0) { - wl_data_source_offer(source->source, - mime_conversion_list[index][0]); - } - } - /* */ - - ++num_offers; + mime_type); } - if (num_offers == 0) { + if (index == 0) { Wayland_data_device_clear_selection(data_device); status = SDL_SetError("No mime data"); } else { @@ -617,36 +571,26 @@ int Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device, } int Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDevice *primary_selection_device, - SDL_WaylandPrimarySelectionSource *source) + SDL_WaylandPrimarySelectionSource *source, + size_t mime_count, + const char **mime_types) { int status = 0; - size_t num_offers = 0; - size_t index = 0; if (primary_selection_device == NULL) { status = SDL_SetError("Invalid Primary Selection Device"); } else if (source == NULL) { status = SDL_SetError("Invalid source"); } else { - SDL_MimeDataList *mime_data = NULL; + size_t index = 0; + const char *mime_type = mime_types[index]; - wl_list_for_each (mime_data, &(source->mimes), link) { - zwp_primary_selection_source_v1_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 (SDL_strcmp(mime_conversion_list[index][1], mime_data->mime_type) == 0) { - zwp_primary_selection_source_v1_offer(source->source, - mime_conversion_list[index][0]); - } - } - /* */ - - ++num_offers; + for (index = 0; index < mime_count; ++index) { + mime_type = mime_types[index]; + zwp_primary_selection_source_v1_offer(source->source, mime_type); } - if (num_offers == 0) { + if (index == 0) { Wayland_primary_selection_device_clear_selection(primary_selection_device); status = SDL_SetError("No mime data"); } else { diff --git a/src/video/wayland/SDL_waylanddatamanager.h b/src/video/wayland/SDL_waylanddatamanager.h index eaee423b9..5a4a31307 100644 --- a/src/video/wayland/SDL_waylanddatamanager.h +++ b/src/video/wayland/SDL_waylanddatamanager.h @@ -38,18 +38,27 @@ typedef struct struct wl_list link; } SDL_MimeDataList; +typedef struct SDL_WaylandUserdata +{ + SDL_bool internal; + void *data; +} SDL_WaylandUserdata; + typedef struct { struct wl_data_source *source; - struct wl_list mimes; void *data_device; + SDL_ClipboardDataCallback callback; + SDL_WaylandUserdata userdata; } SDL_WaylandDataSource; typedef struct { struct zwp_primary_selection_source_v1 *source; - struct wl_list mimes; + void *data_device; void *primary_selection_device; + SDL_ClipboardDataCallback callback; + SDL_WaylandUserdata userdata; } SDL_WaylandPrimarySelectionSource; typedef struct @@ -92,8 +101,6 @@ typedef struct SDL_WaylandPrimarySelectionOffer *selection_offer; } SDL_WaylandPrimarySelectionDevice; -extern const char *Wayland_convert_mime_type(const char *mime_type); - /* Wayland Data Source / Primary Selection Source - (Sending) */ extern SDL_WaylandDataSource *Wayland_data_source_create(SDL_VideoDevice *_this); extern SDL_WaylandPrimarySelectionSource *Wayland_primary_selection_source_create(SDL_VideoDevice *_this); @@ -101,18 +108,13 @@ extern ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source, const char *mime_type, int fd); extern ssize_t Wayland_primary_selection_source_send(SDL_WaylandPrimarySelectionSource *source, const char *mime_type, int fd); -extern int Wayland_data_source_add_data(SDL_WaylandDataSource *source, - const char *mime_type, - const void *buffer, - size_t length); -extern int Wayland_primary_selection_source_add_data(SDL_WaylandPrimarySelectionSource *source, - const char *mime_type, - const void *buffer, - size_t length); -extern SDL_bool Wayland_data_source_has_mime(SDL_WaylandDataSource *source, - const char *mime_type); -extern SDL_bool Wayland_primary_selection_source_has_mime(SDL_WaylandPrimarySelectionSource *source, - const char *mime_type); +extern void Wayland_data_source_set_callback(SDL_WaylandDataSource *source, + SDL_ClipboardDataCallback callback, + void *userdata, + SDL_bool internal); +extern int Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSource *source, + SDL_ClipboardDataCallback callback, + void *userdata); extern void *Wayland_data_source_get_data(SDL_WaylandDataSource *source, size_t *length, const char *mime_type, @@ -148,9 +150,13 @@ extern void Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionO extern int Wayland_data_device_clear_selection(SDL_WaylandDataDevice *device); extern int Wayland_primary_selection_device_clear_selection(SDL_WaylandPrimarySelectionDevice *device); extern int Wayland_data_device_set_selection(SDL_WaylandDataDevice *device, - SDL_WaylandDataSource *source); + SDL_WaylandDataSource *source, + size_t mime_count, + const char **mime_types); extern int Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDevice *device, - SDL_WaylandPrimarySelectionSource *source); + SDL_WaylandPrimarySelectionSource *source, + size_t mime_count, + const char **mime_types); extern int Wayland_data_device_set_serial(SDL_WaylandDataDevice *device, uint32_t serial); extern int Wayland_primary_selection_device_set_serial(SDL_WaylandPrimarySelectionDevice *device, diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index c9ed171e5..845e49003 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -1673,7 +1673,10 @@ static void data_source_handle_send(void *data, struct wl_data_source *wl_data_s static void data_source_handle_cancelled(void *data, struct wl_data_source *wl_data_source) { - Wayland_data_source_destroy(data); + SDL_WaylandDataSource *source = data; + if (source != NULL) { + Wayland_data_source_destroy(source); + } } static void data_source_handle_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source) @@ -1739,7 +1742,6 @@ SDL_WaylandDataSource *Wayland_data_source_create(SDL_VideoDevice *_this) SDL_OutOfMemory(); wl_data_source_destroy(id); } else { - WAYLAND_wl_list_init(&(data_source->mimes)); data_source->source = id; wl_data_source_set_user_data(id, data_source); wl_data_source_add_listener(id, &data_source_listener, @@ -1774,7 +1776,6 @@ SDL_WaylandPrimarySelectionSource *Wayland_primary_selection_source_create(SDL_V SDL_OutOfMemory(); zwp_primary_selection_source_v1_destroy(id); } else { - WAYLAND_wl_list_init(&(primary_selection_source->mimes)); primary_selection_source->source = id; zwp_primary_selection_source_v1_add_listener(id, &primary_selection_source_listener, primary_selection_source); @@ -2728,6 +2729,9 @@ void Wayland_display_destroy_input(SDL_VideoData *d) if (input->primary_selection_device->selection_offer != NULL) { Wayland_primary_selection_offer_destroy(input->primary_selection_device->selection_offer); } + if (input->primary_selection_device->selection_source != NULL) { + Wayland_primary_selection_source_destroy(input->primary_selection_device->selection_source); + } SDL_free(input->primary_selection_device); } diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 65d674003..edb74a0fa 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -269,12 +269,16 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->system_theme = SDL_SystemTheme_Get(); #endif + device->SetClipboardData = Wayland_SetClipboardData; + device->GetClipboardData = Wayland_GetClipboardData; + device->HasClipboardData = Wayland_HasClipboardData; device->SetClipboardText = Wayland_SetClipboardText; device->GetClipboardText = Wayland_GetClipboardText; device->HasClipboardText = Wayland_HasClipboardText; device->SetPrimarySelectionText = Wayland_SetPrimarySelectionText; device->GetPrimarySelectionText = Wayland_GetPrimarySelectionText; device->HasPrimarySelectionText = Wayland_HasPrimarySelectionText; + device->GetClipboardUserdata = Wayland_GetClipboardUserdata; device->StartTextInput = Wayland_StartTextInput; device->StopTextInput = Wayland_StopTextInput; device->SetTextInputRect = Wayland_SetTextInputRect; diff --git a/test/testautomation_clipboard.c b/test/testautomation_clipboard.c index e94be1a73..8d9a229a2 100644 --- a/test/testautomation_clipboard.c +++ b/test/testautomation_clipboard.c @@ -22,6 +22,19 @@ static int clipboard_testHasClipboardText(void *arg) return TEST_COMPLETED; } +/** + * \brief Check call to SDL_HasClipboardData + * + * \sa SDL_HasClipboardData + */ +static int clipboard_testHasClipboardData(void *arg) +{ + SDL_HasClipboardData("image/png"); + SDLTest_AssertPass("Call to SDL_HasClipboardText succeeded"); + + return TEST_COMPLETED; +} + /** * \brief Check call to SDL_HasPrimarySelectionText * @@ -51,6 +64,25 @@ static int clipboard_testGetClipboardText(void *arg) return TEST_COMPLETED; } +/** + * \brief Check call to SDL_GetClipboardData + * + * \sa SDL_GetClipboardText + */ +static int clipboard_testGetClipboardData(void *arg) +{ + void *buffer = NULL; + size_t length; + buffer = SDL_GetClipboardData(&length, "image/png"); + SDLTest_AssertPass("Call to SDL_GetClipboardData succeeded"); + + if (buffer != NULL) { + SDL_free(buffer); + } + + return TEST_COMPLETED; +} + /** * \brief Check call to SDL_GetPrimarySelectionText * @@ -94,6 +126,27 @@ static int clipboard_testSetClipboardText(void *arg) return TEST_COMPLETED; } +/** + * \brief Check call to SDL_SetClipboardData + * \sa SDL_SetClipboardText + */ +static int clipboard_testSetClipboardData(void *arg) +{ + int result = -1; + + result = SDL_SetClipboardData(NULL, 0, NULL, NULL); + SDLTest_AssertPass("Call to SDL_SetClipboardData succeeded"); + SDLTest_AssertCheck( + result == 0, + "Validate SDL_SetClipboardData result, expected 0, got %i", + result); + + SDL_GetClipboardUserdata(); + SDLTest_AssertPass("Call to SDL_GetClipboardUserdata succeeded"); + + return TEST_COMPLETED; +} + /** * \brief Check call to SDL_SetPrimarySelectionText * \sa SDL_SetPrimarySelectionText @@ -310,9 +363,21 @@ static const SDLTest_TestCaseReference clipboardTest8 = { (SDLTest_TestCaseFp)clipboard_testPrimarySelectionTextFunctions, "clipboard_testPrimarySelectionTextFunctions", "End-to-end test of SDL_xyzPrimarySelectionText functions", TEST_ENABLED }; +static const SDLTest_TestCaseReference clipboardTest9 = { + (SDLTest_TestCaseFp)clipboard_testGetClipboardData, "clipboard_testGetClipboardData", "Check call to SDL_GetClipboardData", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference clipboardTest10 = { + (SDLTest_TestCaseFp)clipboard_testSetClipboardData, "clipboard_testSetClipboardData", "Check call to SDL_SetClipboardData", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference clipboardTest11 = { + (SDLTest_TestCaseFp)clipboard_testHasClipboardData, "clipboard_testHasClipboardData", "Check call to SDL_HasClipboardData", TEST_ENABLED +}; + /* Sequence of Clipboard test cases */ static const SDLTest_TestCaseReference *clipboardTests[] = { - &clipboardTest1, &clipboardTest2, &clipboardTest3, &clipboardTest4, &clipboardTest5, &clipboardTest6, &clipboardTest7, &clipboardTest8, NULL + &clipboardTest1, &clipboardTest2, &clipboardTest3, &clipboardTest4, &clipboardTest5, &clipboardTest6, &clipboardTest7, &clipboardTest8, &clipboardTest9, &clipboardTest10, &clipboardTest11, NULL }; /* Clipboard test suite (global) */