clipboard: include mime types in SDL_ClipboarUpdate

This patch modifies the clipboard handling so that when we receive an external
clipboard update, the suppported mime types are included in the SDL_ClipboarUpdate
event. The patch also introduces the owner field that allows to know if the update
is because we own the clipboard (internal update) or if it was an external update.
This commit is contained in:
David Fort 2024-10-02 21:19:42 +02:00 committed by Sam Lantinga
parent 2880b40e33
commit e00b1fdd67
18 changed files with 256 additions and 66 deletions

View file

@ -845,6 +845,9 @@ typedef struct SDL_ClipboardEvent
SDL_EventType type; /**< SDL_EVENT_CLIPBOARD_UPDATE */
Uint32 reserved;
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
bool owner; /**< are we owning the clipboard (internal update) */
Sint32 n_mime_types; /**< number of mime types */
const char **mime_types; /**< current mime types */
} SDL_ClipboardEvent;
/**

View file

@ -1377,7 +1377,8 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
JNIEnv *env, jclass jcls)
{
SDL_SendClipboardUpdate();
// TODO: compute new mime types
SDL_SendClipboardUpdate(false, NULL, 0);
}
// Low memory

View file

@ -25,12 +25,17 @@
#include "SDL_events_c.h"
#include "SDL_clipboardevents_c.h"
void SDL_SendClipboardUpdate(void)
void SDL_SendClipboardUpdate(bool owner, char **mime_types, size_t n_mime_types)
{
if (SDL_EventEnabled(SDL_EVENT_CLIPBOARD_UPDATE)) {
SDL_Event event;
event.type = SDL_EVENT_CLIPBOARD_UPDATE;
event.clipboard.timestamp = 0;
SDL_ClipboardEvent *cevent = &event.clipboard;
cevent->timestamp = 0;
cevent->owner = owner;
cevent->mime_types = (const char **)mime_types;
cevent->n_mime_types = (Uint32)n_mime_types;
SDL_PushEvent(&event);
}
}

View file

@ -23,6 +23,6 @@
#ifndef SDL_clipboardevents_c_h_
#define SDL_clipboardevents_c_h_
extern void SDL_SendClipboardUpdate(void);
extern void SDL_SendClipboardUpdate(bool owner, char **mime_types, size_t n_mime_types);
#endif // SDL_clipboardevents_c_h_

View file

@ -235,6 +235,9 @@ static void SDL_TransferTemporaryMemoryToEvent(SDL_EventEntry *event)
SDL_LinkTemporaryMemoryToEvent(event, event->event.drop.source);
SDL_LinkTemporaryMemoryToEvent(event, event->event.drop.data);
break;
case SDL_EVENT_CLIPBOARD_UPDATE:
SDL_LinkTemporaryMemoryToEvent(event, event->event.clipboard.mime_types);
break;
default:
break;
}

View file

@ -22,13 +22,25 @@
#include "SDL_clipboard_c.h"
#include "SDL_sysvideo.h"
#include "../events/SDL_events_c.h"
#include "../events/SDL_clipboardevents_c.h"
void SDL_FreeClipboardMimeTypes(SDL_VideoDevice *_this)
{
if (_this->clipboard_mime_types) {
for (size_t i = 0; i < _this->num_clipboard_mime_types; ++i) {
SDL_free(_this->clipboard_mime_types[i]);
}
SDL_free(_this->clipboard_mime_types);
_this->clipboard_mime_types = NULL;
_this->num_clipboard_mime_types = 0;
}
}
void SDL_CancelClipboardData(Uint32 sequence)
{
SDL_VideoDevice *_this = SDL_GetVideoDevice();
size_t i;
if (sequence != _this->clipboard_sequence) {
// This clipboard data was already canceled
@ -39,14 +51,7 @@ void SDL_CancelClipboardData(Uint32 sequence)
_this->clipboard_cleanup(_this->clipboard_userdata);
}
if (_this->clipboard_mime_types) {
for (i = 0; i < _this->num_clipboard_mime_types; ++i) {
SDL_free(_this->clipboard_mime_types[i]);
}
SDL_free(_this->clipboard_mime_types);
_this->clipboard_mime_types = NULL;
_this->num_clipboard_mime_types = 0;
}
SDL_FreeClipboardMimeTypes(_this);
_this->clipboard_callback = NULL;
_this->clipboard_cleanup = NULL;
@ -135,7 +140,11 @@ bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardClean
}
}
SDL_SendClipboardUpdate();
char **mime_types_copy = SDL_CopyClipboardMimeTypes(mime_types, num_mime_types, true);
if (!mime_types_copy)
return SDL_SetError("unable to copy current mime types");
SDL_SendClipboardUpdate(true, mime_types_copy, num_mime_types);
return true;
}
@ -232,6 +241,41 @@ bool SDL_HasClipboardData(const char *mime_type)
}
}
char **SDL_CopyClipboardMimeTypes(const char **clipboard_mime_types, size_t num_mime_types, bool temporary)
{
size_t allocSize = sizeof(char *);
for (size_t i = 0; i < num_mime_types; i++) {
allocSize += sizeof(char *) + SDL_strlen(clipboard_mime_types[i]) + 1;
}
char *ret;
if (temporary)
ret = (char *)SDL_AllocateTemporaryMemory(allocSize);
else
ret = (char *)SDL_malloc(allocSize);
if (!ret) {
return NULL;
}
char **result = (char **)ret;
ret += sizeof(char *) * (num_mime_types + 1);
for (size_t i = 0; i < num_mime_types; i++) {
result[i] = ret;
const char *mime_type = clipboard_mime_types[i];
// Copy the whole string including the terminating null char
char c;
do {
c = *ret++ = *mime_type++;
} while (c != '\0');
}
result[num_mime_types] = NULL;
return result;
}
char **SDL_GetClipboardMimeTypes(size_t *num_mime_types)
{
SDL_VideoDevice *_this = SDL_GetVideoDevice();
@ -241,35 +285,8 @@ char **SDL_GetClipboardMimeTypes(size_t *num_mime_types)
return NULL;
}
size_t allocSize = sizeof(char *);
for (size_t i = 0; i < _this->num_clipboard_mime_types; i++) {
allocSize += sizeof(char *) + SDL_strlen(_this->clipboard_mime_types[i]) + 1;
}
char *ret = (char *)SDL_malloc(allocSize);
if (!ret) {
return NULL;
}
char **result = (char **)ret;
ret += sizeof(char *) * (_this->num_clipboard_mime_types + 1);
for (size_t i = 0; i < _this->num_clipboard_mime_types; i++) {
result[i] = ret;
const char *mime_type = _this->clipboard_mime_types[i];
// Copy the whole string including the terminating null char
char c;
do {
c = *ret++ = *mime_type++;
} while (c != '\0');
}
result[_this->num_clipboard_mime_types] = NULL;
if (num_mime_types) {
*num_mime_types = _this->num_clipboard_mime_types;
}
return result;
return SDL_CopyClipboardMimeTypes((const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types, false);
}
// Clipboard text
@ -392,7 +409,11 @@ bool SDL_SetPrimarySelectionText(const char *text)
_this->primary_selection_text = SDL_strdup(text);
}
SDL_SendClipboardUpdate();
char **mime_types = SDL_CopyClipboardMimeTypes((const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types, true);
if (!mime_types)
return SDL_SetError("unable to copy current mime types");
SDL_SendClipboardUpdate(true, mime_types, _this->num_clipboard_mime_types);
return true;
}

View file

@ -39,4 +39,7 @@ extern bool SDL_HasInternalClipboardData(SDL_VideoDevice *_this, const char *mim
// General purpose clipboard text callback
const void * SDLCALL SDL_ClipboardTextCallback(void *userdata, const char *mime_type, size_t *size);
void SDLCALL SDL_FreeClipboardMimeTypes(SDL_VideoDevice *_this);
char ** SDLCALL SDL_CopyClipboardMimeTypes(const char **clipboard_mime_types, size_t num_mime_types, bool temporary);
#endif // SDL_clipboard_c_h_

View file

@ -83,7 +83,8 @@ void Cocoa_CheckClipboardUpdate(SDL_CocoaVideoData *data)
count = [pasteboard changeCount];
if (count != data.clipboard_count) {
if (data.clipboard_count) {
SDL_SendClipboardUpdate();
// TODO: compute mime types
SDL_SendClipboardUpdate(false, NULL, 0);
}
data.clipboard_count = count;
}

View file

@ -80,7 +80,8 @@ void UIKit_InitClipboard(SDL_VideoDevice *_this)
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
SDL_SendClipboardUpdate();
// TODO: compute mime types
SDL_SendClipboardUpdate(false, NULL, 0);
}];
data.pasteboardObserver = observer;

View file

@ -2277,6 +2277,40 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d
data_device->drag_offer = NULL;
}
static void notifyFromMimes(struct wl_list *mimes) {
int nformats = 0;
char **new_mime_types = NULL;
if (mimes) {
nformats = WAYLAND_wl_list_length(mimes);
size_t alloc_size = (nformats + 1) * sizeof(char *);
/* do a first pass to compute allocation size */
SDL_MimeDataList *item = NULL;
wl_list_for_each(item, mimes, link) {
alloc_size += SDL_strlen(item->mime_type) + 1;
}
new_mime_types = SDL_AllocateTemporaryMemory(alloc_size);
if (!new_mime_types) {
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "unable to allocate new_mime_types");
return;
}
/* second pass to fill*/
char *strPtr = (char *)(new_mime_types + nformats + 1);
item = NULL;
int i = 0;
wl_list_for_each(item, mimes, link) {
new_mime_types[i] = strPtr;
strPtr = stpcpy(strPtr, item->mime_type) + 1;
i++;
}
new_mime_types[nformats] = NULL;
}
SDL_SendClipboardUpdate(false, new_mime_types, nformats);
}
static void data_device_handle_selection(void *data, struct wl_data_device *wl_data_device,
struct wl_data_offer *id)
{
@ -2295,7 +2329,7 @@ static void data_device_handle_selection(void *data, struct wl_data_device *wl_d
data_device->selection_offer = offer;
}
SDL_SendClipboardUpdate();
notifyFromMimes(offer ? &offer->mimes : NULL);
}
static const struct wl_data_device_listener data_device_listener = {
@ -2341,7 +2375,7 @@ static void primary_selection_device_handle_selection(void *data, struct zwp_pri
". In zwp_primary_selection_device_v1_listener . primary_selection_device_handle_selection on primary_selection_offer 0x%08x\n",
(id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)id) : -1));
SDL_SendClipboardUpdate();
notifyFromMimes(offer ? &offer->mimes : NULL);
}
static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {

View file

@ -25,6 +25,7 @@
#include "SDL_windowsvideo.h"
#include "SDL_windowswindow.h"
#include "../SDL_clipboard_c.h"
#include "../../events/SDL_events_c.h"
#include "../../events/SDL_clipboardevents_c.h"
#ifdef UNICODE
@ -329,14 +330,73 @@ bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
return false;
}
static char **GetMimeTypes(int *pnformats)
{
*pnformats = 0;
int nformats = CountClipboardFormats();
size_t allocSize = (nformats + 1) * sizeof(char*);
UINT format = 0;
int formatsSz = 0;
int i;
for (i = 0; i < nformats; i++) {
format = EnumClipboardFormats(format);
if (!format) {
nformats = i;
break;
}
char mimeType[200];
int nchars = GetClipboardFormatNameA(format, mimeType, sizeof(mimeType));
formatsSz += nchars + 1;
}
char **new_mime_types = SDL_AllocateTemporaryMemory(allocSize + formatsSz);
if (!new_mime_types)
return NULL;
format = 0;
char *strPtr = (char *)(new_mime_types + nformats + 1);
int formatRemains = formatsSz;
for (i = 0; i < nformats; i++) {
format = EnumClipboardFormats(format);
if (!format) {
nformats = i;
break;
}
new_mime_types[i] = strPtr;
int nchars = GetClipboardFormatNameA(format, strPtr, formatRemains-1);
strPtr += nchars;
*strPtr = '\0';
strPtr++;
formatRemains -= (nchars + 1);
}
new_mime_types[nformats] = NULL;
*pnformats = nformats;
return new_mime_types;
}
void WIN_CheckClipboardUpdate(struct SDL_VideoData *data)
{
const DWORD count = GetClipboardSequenceNumber();
if (count != data->clipboard_count) {
const DWORD seq = GetClipboardSequenceNumber();
if (seq != data->clipboard_count) {
if (data->clipboard_count) {
SDL_SendClipboardUpdate();
int nformats = 0;
char **new_mime_types = GetMimeTypes(&nformats);
if (new_mime_types) {
SDL_SendClipboardUpdate(false, new_mime_types, nformats);
} else {
WIN_SetError("Couldn't get clipboard mime types");
}
data->clipboard_count = count;
}
data->clipboard_count = seq;
}
}

View file

@ -38,7 +38,7 @@ static const char *text_mime_types[] = {
};
// Get any application owned window handle for clipboard association
static Window GetWindow(SDL_VideoDevice *_this)
Window GetWindow(SDL_VideoDevice *_this)
{
SDL_VideoData *data = _this->internal;

View file

@ -41,5 +41,6 @@ extern bool X11_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text
extern char *X11_GetPrimarySelectionText(SDL_VideoDevice *_this);
extern bool X11_HasPrimarySelectionText(SDL_VideoDevice *_this);
extern void X11_QuitClipboard(SDL_VideoDevice *_this);
Window GetWindow(SDL_VideoDevice *_this);
#endif // SDL_x11clipboard_h_

View file

@ -415,7 +415,7 @@ static void DispatchWindowMove(SDL_VideoDevice *_this, const SDL_WindowData *dat
evt.xclient.type = ClientMessage;
evt.xclient.window = data->xwindow;
evt.xclient.message_type = X11_XInternAtom(display, "_NET_WM_MOVERESIZE", True);
evt.xclient.message_type = videodata->atoms._NET_WM_MOVERESIZE;
evt.xclient.format = 32;
evt.xclient.data.l[0] = (size_t)window->x + point->x;
evt.xclient.data.l[1] = (size_t)window->y + point->y;
@ -450,7 +450,7 @@ static void InitiateWindowResize(SDL_VideoDevice *_this, const SDL_WindowData *d
evt.xclient.type = ClientMessage;
evt.xclient.window = data->xwindow;
evt.xclient.message_type = X11_XInternAtom(display, "_NET_WM_MOVERESIZE", True);
evt.xclient.message_type = videodata->atoms._NET_WM_MOVERESIZE;
evt.xclient.format = 32;
evt.xclient.data.l[0] = (size_t)window->x + point->x;
evt.xclient.data.l[1] = (size_t)window->y + point->y;
@ -553,7 +553,7 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven
int mime_formats;
unsigned char *seln_data;
size_t seln_length = 0;
Atom XA_TARGETS = X11_XInternAtom(display, "TARGETS", 0);
Atom XA_TARGETS = videodata->atoms.TARGETS;
SDLX11_ClipboardData *clipboard;
#ifdef DEBUG_XEVENTS
@ -627,17 +627,52 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven
case SelectionNotify:
{
const XSelectionEvent *xsel = &xevent->xselection;
#ifdef DEBUG_XEVENTS
SDL_Log("window CLIPBOARD: SelectionNotify (requestor = 0x%lx, target = 0x%lx)\n",
xevent->xselection.requestor, xevent->xselection.target);
const char *propName = xsel->property ? X11_XGetAtomName(display, xsel->property) : "None";
const char *targetName = xsel->target ? X11_XGetAtomName(display, xsel->target) : "None";
SDL_Log("window CLIPBOARD: SelectionNotify (requestor = 0x%lx, target = %s, property = %s)\n",
xsel->requestor, targetName, propName);
#endif
if (xsel->target == videodata->atoms.TARGETS && xsel->property == videodata->atoms.SDL_FORMATS) {
/* the new mime formats are the SDL_FORMATS property as an array of Atoms */
Atom atom = None;
Atom *patom;
unsigned char* data = NULL;
int format_property = 0;
unsigned long length = 0;
unsigned long bytes_left = 0;
int j;
X11_XGetWindowProperty(display, GetWindow(_this), videodata->atoms.SDL_FORMATS, 0, 200,
0, XA_ATOM, &atom, &format_property, &length, &bytes_left, &data);
int allocationsize = (length + 1) * sizeof(char*);
for (j = 0, patom = (Atom*)data; j < length; j++, patom++) {
allocationsize += SDL_strlen( X11_XGetAtomName(display, *patom) ) + 1;
}
char **new_mime_types = SDL_AllocateTemporaryMemory(allocationsize);
if (new_mime_types) {
char *strPtr = (char *)(new_mime_types + length + 1);
for (j = 0, patom = (Atom*)data; j < length; j++, patom++) {
new_mime_types[j] = strPtr;
strPtr = stpcpy(strPtr, X11_XGetAtomName(display, *patom)) + 1;
}
new_mime_types[length] = NULL;
SDL_SendClipboardUpdate(false, new_mime_types, length);
}
}
videodata->selection_waiting = false;
} break;
case SelectionClear:
{
// !!! FIXME: cache atoms
Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0);
Atom XA_CLIPBOARD = videodata->atoms.CLIPBOARD;
SDLX11_ClipboardData *clipboard = NULL;
#ifdef DEBUG_XEVENTS
@ -994,11 +1029,25 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
X11_XGetAtomName(display, ev->selection));
#endif
if (ev->selection == XA_PRIMARY ||
(videodata->atoms.CLIPBOARD != None && ev->selection == videodata->atoms.CLIPBOARD)) {
SDL_SendClipboardUpdate();
if (ev->subtype == XFixesSetSelectionOwnerNotify)
{
if (ev->selection != videodata->atoms.CLIPBOARD)
return;
if (X11_XGetSelectionOwner(display, ev->selection) == videodata->clipboard_window)
return;
/* when here we're notified that the clipboard had an external change, we request the
* available mime types by asking for a conversion to the TARGETS format. We should get a
* SelectionNotify event later, and when treating these results, we will push a ClipboardUpdated
* event
*/
X11_XConvertSelection(display, videodata->atoms.CLIPBOARD, videodata->atoms.TARGETS,
videodata->atoms.SDL_FORMATS, GetWindow(_this), CurrentTime);
}
return;
}
#endif // SDL_VIDEO_DRIVER_X11_XFIXES

View file

@ -403,6 +403,7 @@ static bool X11_VideoInit(SDL_VideoDevice *_this)
GET_ATOM(_NET_WM_STATE_ABOVE);
GET_ATOM(_NET_WM_STATE_SKIP_TASKBAR);
GET_ATOM(_NET_WM_STATE_SKIP_PAGER);
GET_ATOM(_NET_WM_MOVERESIZE);
GET_ATOM(_NET_WM_STATE_MODAL);
GET_ATOM(_NET_WM_ALLOWED_ACTIONS);
GET_ATOM(_NET_WM_ACTION_FULLSCREEN);
@ -420,6 +421,9 @@ static bool X11_VideoInit(SDL_VideoDevice *_this)
GET_ATOM(CLIPBOARD);
GET_ATOM(INCR);
GET_ATOM(SDL_SELECTION);
GET_ATOM(TARGETS);
GET_ATOM(SDL_FORMATS);
GET_ATOM(XdndAware);
GET_ATOM(XdndEnter);
GET_ATOM(XdndPosition);
GET_ATOM(XdndStatus);

View file

@ -82,6 +82,7 @@ struct SDL_VideoData
Atom _NET_WM_STATE_SKIP_TASKBAR;
Atom _NET_WM_STATE_SKIP_PAGER;
Atom _NET_WM_STATE_MODAL;
Atom _NET_WM_MOVERESIZE;
Atom _NET_WM_ALLOWED_ACTIONS;
Atom _NET_WM_ACTION_FULLSCREEN;
Atom _NET_WM_NAME;
@ -98,6 +99,9 @@ struct SDL_VideoData
Atom CLIPBOARD;
Atom INCR;
Atom SDL_SELECTION;
Atom TARGETS;
Atom SDL_FORMATS;
Atom XdndAware;
Atom XdndEnter;
Atom XdndPosition;
Atom XdndStatus;

View file

@ -2001,7 +2001,7 @@ void X11_AcceptDragAndDrop(SDL_Window *window, bool accept)
{
SDL_WindowData *data = window->internal;
Display *display = data->videodata->display;
Atom XdndAware = X11_XInternAtom(display, "XdndAware", False);
Atom XdndAware = data->videodata->atoms.XdndAware;
if (accept) {
Atom xdnd_version = 5;

View file

@ -51,7 +51,7 @@ void X11_InitXfixes(SDL_VideoDevice *_this)
int event, error;
int fixes_opcode;
Atom XA_CLIPBOARD = X11_XInternAtom(data->display, "CLIPBOARD", 0);
Atom XA_CLIPBOARD = data->atoms.CLIPBOARD;
if (!SDL_X11_HAVE_XFIXES ||
!X11_XQueryExtension(data->display, "XFIXES", &fixes_opcode, &event, &error)) {