#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_X11
#include <limits.h>
#include "SDL_x11video.h"
#include "SDL_x11clipboard.h"
#include "../SDL_clipboard_c.h"
#include "../../events/SDL_events_c.h"
static const char *text_mime_types[] = {
"UTF8_STRING",
"text/plain;charset=utf-8",
"text/plain",
"TEXT",
"STRING"
};
Window GetWindow(SDL_VideoDevice *_this)
{
SDL_VideoData *data = _this->internal;
if (data->clipboard_window == None) {
Display *dpy = data->display;
Window parent = RootWindow(dpy, DefaultScreen(dpy));
XSetWindowAttributes xattr;
data->clipboard_window = X11_XCreateWindow(dpy, parent, -10, -10, 1, 1, 0,
CopyFromParent, InputOnly,
NULL, 0, &xattr);
X11_XSelectInput(dpy, data->clipboard_window, PropertyChangeMask);
X11_XFlush(data->display);
}
return data->clipboard_window;
}
static bool SetSelectionData(SDL_VideoDevice *_this, Atom selection, SDL_ClipboardDataCallback callback,
void *userdata, const char **mime_types, size_t mime_count, Uint32 sequence)
{
SDL_VideoData *videodata = _this->internal;
Display *display = videodata->display;
Window window;
SDLX11_ClipboardData *clipboard;
bool clipboard_owner = false;
window = GetWindow(_this);
if (window == None) {
return SDL_SetError("Couldn't find a window to own the selection");
}
if (selection == XA_PRIMARY) {
clipboard = &videodata->primary_selection;
} else {
clipboard = &videodata->clipboard;
}
clipboard_owner = X11_XGetSelectionOwner(display, selection) == window;
if (clipboard_owner && clipboard->sequence == 0) {
SDL_free(clipboard->userdata);
}
clipboard->callback = callback;
clipboard->userdata = userdata;
clipboard->mime_types = mime_types;
clipboard->mime_count = mime_count;
clipboard->sequence = sequence;
X11_XSetSelectionOwner(display, selection, window, CurrentTime);
return true;
}
static void *CloneDataBuffer(const void *buffer, const size_t len)
{
void *clone = NULL;
if (len > 0 && buffer) {
clone = SDL_malloc(len + sizeof(Uint32));
if (clone) {
SDL_memcpy(clone, buffer, len);
SDL_memset((Uint8 *)clone + len, 0, sizeof(Uint32));
}
}
return clone;
}
static void *AppendDataBuffer(void *original_buffer, const size_t old_len, const void *buffer, const size_t buffer_len)
{
void *resized_buffer;
if (buffer_len > 0 && buffer) {
resized_buffer = SDL_realloc(original_buffer, old_len + buffer_len + sizeof(Uint32));
if (resized_buffer) {
SDL_memcpy((Uint8 *)resized_buffer + old_len, buffer, buffer_len);
SDL_memset((Uint8 *)resized_buffer + old_len + buffer_len, 0, sizeof(Uint32));
}
return resized_buffer;
} else {
return original_buffer;
}
}
static bool WaitForSelection(SDL_VideoDevice *_this, Atom selection_type, bool *flag)
{
Uint64 waitStart;
Uint64 waitElapsed;
waitStart = SDL_GetTicks();
*flag = true;
while (*flag) {
X11_PumpEvents(_this);
waitElapsed = SDL_GetTicks() - waitStart;
if (waitElapsed > 1000) {
*flag = false;
SDL_SetError("Selection timeout");
SetSelectionData(_this, selection_type, SDL_ClipboardTextCallback, NULL,
text_mime_types, SDL_arraysize(text_mime_types), 0);
return false;
}
}
return true;
}
static void *GetSelectionData(SDL_VideoDevice *_this, Atom selection_type,
const char *mime_type, size_t *length)
{
SDL_VideoData *videodata = _this->internal;
Display *display = videodata->display;
Window window;
Window owner;
Atom selection;
Atom seln_type;
int seln_format;
unsigned long count;
unsigned long overflow;
SDLX11_ClipboardData *clipboard;
void *data = NULL;
unsigned char *src = NULL;
bool incr_success = false;
Atom XA_MIME = X11_XInternAtom(display, mime_type, False);
*length = 0;
window = GetWindow(_this);
owner = X11_XGetSelectionOwner(display, selection_type);
if (owner == None) {
data = NULL;
} else if (owner == window) {
owner = DefaultRootWindow(display);
if (selection_type == XA_PRIMARY) {
clipboard = &videodata->primary_selection;
} else {
clipboard = &videodata->clipboard;
}
if (clipboard->callback) {
const void *clipboard_data = clipboard->callback(clipboard->userdata, mime_type, length);
data = CloneDataBuffer(clipboard_data, *length);
}
} else {
owner = window;
selection = videodata->atoms.SDL_SELECTION;
X11_XConvertSelection(display, selection_type, XA_MIME, selection, owner,
CurrentTime);
if (WaitForSelection(_this, selection_type, &videodata->selection_waiting) == false) {
data = NULL;
*length = 0;
}
if (X11_XGetWindowProperty(display, owner, selection, 0, INT_MAX / 4, False,
XA_MIME, &seln_type, &seln_format, &count, &overflow, &src) == Success) {
if (seln_type == XA_MIME) {
*length = (size_t)count;
data = CloneDataBuffer(src, count);
} else if (seln_type == videodata->atoms.INCR) {
while (1) {
X11_XDeleteProperty(display, owner, selection);
X11_XFlush(display);
if (WaitForSelection(_this, selection_type, &videodata->selection_incr_waiting) == false) {
break;
}
X11_XFree(src);
if (X11_XGetWindowProperty(display, owner, selection, 0, INT_MAX / 4, False,
XA_MIME, &seln_type, &seln_format, &count, &overflow, &src) != Success) {
break;
}
if (count == 0) {
incr_success = true;
break;
}
if (*length == 0) {
*length = (size_t)count;
data = CloneDataBuffer(src, count);
} else {
data = AppendDataBuffer(data, *length, src, count);
*length += (size_t)count;
}
if (data == NULL) {
break;
}
}
if (incr_success == false) {
SDL_free(data);
data = NULL;
*length = 0;
}
}
X11_XFree(src);
}
}
return data;
}
const char **X11_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types)
{
*num_mime_types = SDL_arraysize(text_mime_types);
return text_mime_types;
}
bool X11_SetClipboardData(SDL_VideoDevice *_this)
{
SDL_VideoData *videodata = _this->internal;
return SetSelectionData(_this, videodata->atoms.CLIPBOARD, _this->clipboard_callback, _this->clipboard_userdata, (const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types, _this->clipboard_sequence);
}
void *X11_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *length)
{
SDL_VideoData *videodata = _this->internal;
*length = 0;
if (!SDL_HasInternalClipboardData(_this, mime_type)) {
return NULL;
}
return GetSelectionData(_this, videodata->atoms.CLIPBOARD, mime_type, length);
}
bool X11_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
{
size_t length;
void *data;
data = X11_GetClipboardData(_this, mime_type, &length);
SDL_free(data);
return length > 0;
}
bool X11_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text)
{
return SetSelectionData(_this, XA_PRIMARY, SDL_ClipboardTextCallback, SDL_strdup(text), text_mime_types, SDL_arraysize(text_mime_types), 0);
}
char *X11_GetPrimarySelectionText(SDL_VideoDevice *_this)
{
size_t length;
char *text = GetSelectionData(_this, XA_PRIMARY, text_mime_types[0], &length);
if (!text) {
text = SDL_strdup("");
}
return text;
}
bool X11_HasPrimarySelectionText(SDL_VideoDevice *_this)
{
bool result = false;
char *text = X11_GetPrimarySelectionText(_this);
if (text) {
if (text[0] != '\0') {
result = true;
}
SDL_free(text);
}
return result;
}
void X11_QuitClipboard(SDL_VideoDevice *_this)
{
SDL_VideoData *data = _this->internal;
if (data->primary_selection.sequence == 0) {
SDL_free(data->primary_selection.userdata);
}
if (data->clipboard.sequence == 0) {
SDL_free(data->clipboard.userdata);
}
}
#endif