Atlas - SDL_x11clipboard.c
Home / ext / SDL / src / video / x11 Lines: 1 | Size: 11271 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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#include "SDL_internal.h" 22 23#ifdef SDL_VIDEO_DRIVER_X11 24 25#include <limits.h> // For INT_MAX 26 27#include "SDL_x11video.h" 28#include "SDL_x11clipboard.h" 29#include "../SDL_clipboard_c.h" 30#include "../../events/SDL_events_c.h" 31 32static const char *text_mime_types[] = { 33 "UTF8_STRING", 34 "text/plain;charset=utf-8", 35 "text/plain", 36 "TEXT", 37 "STRING" 38}; 39 40// Get any application owned window handle for clipboard association 41Window GetWindow(SDL_VideoDevice *_this) 42{ 43 SDL_VideoData *data = _this->internal; 44 45 /* We create an unmapped window that exists just to manage the clipboard, 46 since X11 selection data is tied to a specific window and dies with it. 47 We create the window on demand, so apps that don't use the clipboard 48 don't have to keep an unnecessary resource around. */ 49 if (data->clipboard_window == None) { 50 Display *dpy = data->display; 51 Window parent = RootWindow(dpy, DefaultScreen(dpy)); 52 XSetWindowAttributes xattr; 53 data->clipboard_window = X11_XCreateWindow(dpy, parent, -10, -10, 1, 1, 0, 54 CopyFromParent, InputOnly, 55 NULL, 0, &xattr); 56 57 X11_XSelectInput(dpy, data->clipboard_window, PropertyChangeMask); 58 X11_XFlush(data->display); 59 } 60 61 return data->clipboard_window; 62} 63 64static bool SetSelectionData(SDL_VideoDevice *_this, Atom selection, SDL_ClipboardDataCallback callback, 65 void *userdata, const char **mime_types, size_t mime_count, Uint32 sequence) 66{ 67 SDL_VideoData *videodata = _this->internal; 68 Display *display = videodata->display; 69 Window window; 70 SDLX11_ClipboardData *clipboard; 71 bool clipboard_owner = false; 72 73 window = GetWindow(_this); 74 if (window == None) { 75 return SDL_SetError("Couldn't find a window to own the selection"); 76 } 77 78 if (selection == XA_PRIMARY) { 79 clipboard = &videodata->primary_selection; 80 } else { 81 clipboard = &videodata->clipboard; 82 } 83 84 clipboard_owner = X11_XGetSelectionOwner(display, selection) == window; 85 86 // If we are canceling our own data we need to clean it up 87 if (clipboard_owner && clipboard->sequence == 0) { 88 SDL_free(clipboard->userdata); 89 } 90 91 clipboard->callback = callback; 92 clipboard->userdata = userdata; 93 clipboard->mime_types = mime_types; 94 clipboard->mime_count = mime_count; 95 clipboard->sequence = sequence; 96 97 X11_XSetSelectionOwner(display, selection, window, CurrentTime); 98 return true; 99} 100 101static void *CloneDataBuffer(const void *buffer, const size_t len) 102{ 103 void *clone = NULL; 104 if (len > 0 && buffer) { 105 clone = SDL_malloc(len + sizeof(Uint32)); 106 if (clone) { 107 SDL_memcpy(clone, buffer, len); 108 SDL_memset((Uint8 *)clone + len, 0, sizeof(Uint32)); 109 } 110 } 111 return clone; 112} 113 114/* 115 * original_buffer is considered unusable after the function is called. 116 */ 117static void *AppendDataBuffer(void *original_buffer, const size_t old_len, const void *buffer, const size_t buffer_len) 118{ 119 void *resized_buffer; 120 121 if (buffer_len > 0 && buffer) { 122 resized_buffer = SDL_realloc(original_buffer, old_len + buffer_len + sizeof(Uint32)); 123 if (resized_buffer) { 124 SDL_memcpy((Uint8 *)resized_buffer + old_len, buffer, buffer_len); 125 SDL_memset((Uint8 *)resized_buffer + old_len + buffer_len, 0, sizeof(Uint32)); 126 } 127 128 return resized_buffer; 129 } else { 130 return original_buffer; 131 } 132} 133 134static bool WaitForSelection(SDL_VideoDevice *_this, Atom selection_type, bool *flag) 135{ 136 Uint64 waitStart; 137 Uint64 waitElapsed; 138 139 waitStart = SDL_GetTicks(); 140 *flag = true; 141 while (*flag) { 142 X11_PumpEvents(_this); 143 waitElapsed = SDL_GetTicks() - waitStart; 144 // Wait one second for a selection response. 145 if (waitElapsed > 1000) { 146 *flag = false; 147 SDL_SetError("Selection timeout"); 148 /* We need to set the selection text so that next time we won't 149 timeout, otherwise we will hang on every call to this function. */ 150 SetSelectionData(_this, selection_type, SDL_ClipboardTextCallback, NULL, 151 text_mime_types, SDL_arraysize(text_mime_types), 0); 152 return false; 153 } 154 } 155 156 return true; 157} 158 159static void *GetSelectionData(SDL_VideoDevice *_this, Atom selection_type, 160 const char *mime_type, size_t *length) 161{ 162 SDL_VideoData *videodata = _this->internal; 163 Display *display = videodata->display; 164 Window window; 165 Window owner; 166 Atom selection; 167 Atom seln_type; 168 int seln_format; 169 unsigned long count; 170 unsigned long overflow; 171 172 SDLX11_ClipboardData *clipboard; 173 void *data = NULL; 174 unsigned char *src = NULL; 175 bool incr_success = false; 176 Atom XA_MIME = X11_XInternAtom(display, mime_type, False); 177 178 *length = 0; 179 180 // Get the window that holds the selection 181 window = GetWindow(_this); 182 owner = X11_XGetSelectionOwner(display, selection_type); 183 if (owner == None) { 184 // This requires a fallback to ancient X10 cut-buffers. We will just skip those for now 185 data = NULL; 186 } else if (owner == window) { 187 owner = DefaultRootWindow(display); 188 if (selection_type == XA_PRIMARY) { 189 clipboard = &videodata->primary_selection; 190 } else { 191 clipboard = &videodata->clipboard; 192 } 193 194 if (clipboard->callback) { 195 const void *clipboard_data = clipboard->callback(clipboard->userdata, mime_type, length); 196 data = CloneDataBuffer(clipboard_data, *length); 197 } 198 } else { 199 // Request that the selection owner copy the data to our window 200 owner = window; 201 selection = videodata->atoms.SDL_SELECTION; 202 X11_XConvertSelection(display, selection_type, XA_MIME, selection, owner, 203 CurrentTime); 204 205 if (WaitForSelection(_this, selection_type, &videodata->selection_waiting) == false) { 206 data = NULL; 207 *length = 0; 208 } 209 210 if (X11_XGetWindowProperty(display, owner, selection, 0, INT_MAX / 4, False, 211 XA_MIME, &seln_type, &seln_format, &count, &overflow, &src) == Success) { 212 if (seln_type == XA_MIME) { 213 *length = (size_t)count; 214 data = CloneDataBuffer(src, count); 215 } else if (seln_type == videodata->atoms.INCR) { 216 while (1) { 217 // Only delete the property after being done with the previous "chunk". 218 X11_XDeleteProperty(display, owner, selection); 219 X11_XFlush(display); 220 221 if (WaitForSelection(_this, selection_type, &videodata->selection_incr_waiting) == false) { 222 break; 223 } 224 225 X11_XFree(src); 226 if (X11_XGetWindowProperty(display, owner, selection, 0, INT_MAX / 4, False, 227 XA_MIME, &seln_type, &seln_format, &count, &overflow, &src) != Success) { 228 break; 229 } 230 231 if (count == 0) { 232 incr_success = true; 233 break; 234 } 235 236 if (*length == 0) { 237 *length = (size_t)count; 238 data = CloneDataBuffer(src, count); 239 } else { 240 data = AppendDataBuffer(data, *length, src, count); 241 *length += (size_t)count; 242 } 243 244 if (data == NULL) { 245 break; 246 } 247 } 248 249 if (incr_success == false) { 250 SDL_free(data); 251 data = NULL; 252 *length = 0; 253 } 254 } 255 X11_XFree(src); 256 } 257 } 258 return data; 259} 260 261const char **X11_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types) 262{ 263 *num_mime_types = SDL_arraysize(text_mime_types); 264 return text_mime_types; 265} 266 267bool X11_SetClipboardData(SDL_VideoDevice *_this) 268{ 269 SDL_VideoData *videodata = _this->internal; 270 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); 271} 272 273void *X11_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *length) 274{ 275 SDL_VideoData *videodata = _this->internal; 276 277 *length = 0; 278 279 if (!SDL_HasInternalClipboardData(_this, mime_type)) { 280 // This mime type wasn't advertised by the last selection owner. 281 // The atom might still have data, but it's stale, so ignore it. 282 return NULL; 283 } 284 return GetSelectionData(_this, videodata->atoms.CLIPBOARD, mime_type, length); 285} 286 287bool X11_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type) 288{ 289 size_t length; 290 void *data; 291 data = X11_GetClipboardData(_this, mime_type, &length); 292 SDL_free(data); 293 return length > 0; 294} 295 296bool X11_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text) 297{ 298 return SetSelectionData(_this, XA_PRIMARY, SDL_ClipboardTextCallback, SDL_strdup(text), text_mime_types, SDL_arraysize(text_mime_types), 0); 299} 300 301char *X11_GetPrimarySelectionText(SDL_VideoDevice *_this) 302{ 303 size_t length; 304 char *text = GetSelectionData(_this, XA_PRIMARY, text_mime_types[0], &length); 305 if (!text) { 306 text = SDL_strdup(""); 307 } 308 return text; 309} 310 311bool X11_HasPrimarySelectionText(SDL_VideoDevice *_this) 312{ 313 bool result = false; 314 char *text = X11_GetPrimarySelectionText(_this); 315 if (text) { 316 if (text[0] != '\0') { 317 result = true; 318 } 319 SDL_free(text); 320 } 321 return result; 322} 323 324void X11_QuitClipboard(SDL_VideoDevice *_this) 325{ 326 SDL_VideoData *data = _this->internal; 327 if (data->primary_selection.sequence == 0) { 328 SDL_free(data->primary_selection.userdata); 329 } 330 if (data->clipboard.sequence == 0) { 331 SDL_free(data->clipboard.userdata); 332 } 333} 334 335#endif // SDL_VIDEO_DRIVER_X11 336[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.