1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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#include "SDL_clipboard_c.h"
24#include "SDL_sysvideo.h"
25#include "../events/SDL_events_c.h"
26#include "../events/SDL_clipboardevents_c.h"
27
28void SDL_FreeClipboardMimeTypes(SDL_VideoDevice *_this)
29{
30 if (_this->clipboard_mime_types) {
31 for (size_t i = 0; i < _this->num_clipboard_mime_types; ++i) {
32 SDL_free(_this->clipboard_mime_types[i]);
33 }
34 SDL_free(_this->clipboard_mime_types);
35 _this->clipboard_mime_types = NULL;
36 _this->num_clipboard_mime_types = 0;
37 }
38}
39
40
41void SDL_CancelClipboardData(Uint32 sequence)
42{
43 SDL_VideoDevice *_this = SDL_GetVideoDevice();
44
45 if (sequence && sequence != _this->clipboard_sequence) {
46 // This clipboard data was already canceled
47 return;
48 }
49
50 if (_this->clipboard_cleanup) {
51 _this->clipboard_cleanup(_this->clipboard_userdata);
52 }
53
54 SDL_FreeClipboardMimeTypes(_this);
55
56 _this->clipboard_callback = NULL;
57 _this->clipboard_cleanup = NULL;
58 _this->clipboard_userdata = NULL;
59}
60
61bool SDL_SaveClipboardMimeTypes(const char **mime_types, size_t num_mime_types)
62{
63 SDL_VideoDevice *_this = SDL_GetVideoDevice();
64
65 SDL_FreeClipboardMimeTypes(_this);
66
67 if (mime_types && num_mime_types > 0) {
68 size_t num_allocated = 0;
69
70 _this->clipboard_mime_types = (char **)SDL_malloc(num_mime_types * sizeof(char *));
71 if (_this->clipboard_mime_types) {
72 for (size_t i = 0; i < num_mime_types; ++i) {
73 _this->clipboard_mime_types[i] = SDL_strdup(mime_types[i]);
74 if (_this->clipboard_mime_types[i]) {
75 ++num_allocated;
76 }
77 }
78 }
79 if (num_allocated < num_mime_types) {
80 SDL_FreeClipboardMimeTypes(_this);
81 return false;
82 }
83 _this->num_clipboard_mime_types = num_mime_types;
84 }
85 return true;
86}
87
88bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardCleanupCallback cleanup, void *userdata, const char **mime_types, size_t num_mime_types)
89{
90 SDL_VideoDevice *_this = SDL_GetVideoDevice();
91
92 if (!_this) {
93 return SDL_UninitializedVideo();
94 }
95
96 // Parameter validation
97 if (!((callback && mime_types && num_mime_types > 0) ||
98 (!callback && !mime_types && num_mime_types == 0))) {
99 return SDL_SetError("Invalid parameters");
100 }
101
102 SDL_CancelClipboardData(0);
103
104 ++_this->clipboard_sequence;
105 if (!_this->clipboard_sequence) {
106 _this->clipboard_sequence = 1;
107 }
108 _this->clipboard_callback = callback;
109 _this->clipboard_cleanup = cleanup;
110 _this->clipboard_userdata = userdata;
111
112 if (!SDL_SaveClipboardMimeTypes(mime_types, num_mime_types)) {
113 SDL_ClearClipboardData();
114 return false;
115 }
116
117 if (_this->SetClipboardData) {
118 if (!_this->SetClipboardData(_this)) {
119 return false;
120 }
121 } else if (_this->SetClipboardText) {
122 char *text = NULL;
123 size_t size;
124
125 for (size_t i = 0; i < num_mime_types; ++i) {
126 const char *mime_type = _this->clipboard_mime_types[i];
127 if (SDL_IsTextMimeType(mime_type)) {
128 const void *data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &size);
129 if (data) {
130 text = (char *)SDL_malloc(size + 1);
131 SDL_memcpy(text, data, size);
132 text[size] = '\0';
133 if (!_this->SetClipboardText(_this, text)) {
134 SDL_free(text);
135 return false;
136 }
137 break;
138 }
139 }
140 }
141 if (text) {
142 SDL_free(text);
143 } else {
144 if (!_this->SetClipboardText(_this, "")) {
145 return false;
146 }
147 }
148 }
149
150 char **mime_types_copy = SDL_CopyClipboardMimeTypes(mime_types, num_mime_types, true);
151 if (!mime_types_copy)
152 return SDL_SetError("unable to copy current mime types");
153
154 SDL_SendClipboardUpdate(true, mime_types_copy, num_mime_types);
155 return true;
156}
157
158bool SDL_ClearClipboardData(void)
159{
160 return SDL_SetClipboardData(NULL, NULL, NULL, NULL, 0);
161}
162
163void *SDL_GetInternalClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size)
164{
165 void *data = NULL;
166
167 if (_this->clipboard_callback) {
168 const void *provided_data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, size);
169 if (provided_data) {
170 // Make a copy of it for the caller and guarantee null termination
171 data = SDL_malloc(*size + sizeof(Uint32));
172 if (data) {
173 SDL_memcpy(data, provided_data, *size);
174 SDL_memset((Uint8 *)data + *size, 0, sizeof(Uint32));
175 }
176 }
177 }
178 return data;
179}
180
181void *SDL_GetClipboardData(const char *mime_type, size_t *size)
182{
183 SDL_VideoDevice *_this = SDL_GetVideoDevice();
184 size_t unused;
185
186 if (!_this) {
187 SDL_UninitializedVideo();
188 return NULL;
189 }
190
191 if (!mime_type) {
192 SDL_InvalidParamError("mime_type");
193 return NULL;
194 }
195 if (!size) {
196 size = &unused;
197 }
198
199 // Initialize size to empty, so implementations don't have to worry about it
200 *size = 0;
201
202 if (_this->GetClipboardData) {
203 return _this->GetClipboardData(_this, mime_type, size);
204 } else if (_this->GetClipboardText && SDL_IsTextMimeType(mime_type)) {
205 char *text = _this->GetClipboardText(_this);
206 if (text) {
207 if (*text == '\0') {
208 SDL_free(text);
209 text = NULL;
210 } else {
211 *size = SDL_strlen(text);
212 }
213 }
214 return text;
215 } else {
216 return SDL_GetInternalClipboardData(_this, mime_type, size);
217 }
218}
219
220bool SDL_HasInternalClipboardData(SDL_VideoDevice *_this, const char *mime_type)
221{
222 size_t i;
223
224 for (i = 0; i < _this->num_clipboard_mime_types; ++i) {
225 if (SDL_strcmp(mime_type, _this->clipboard_mime_types[i]) == 0) {
226 return true;
227 }
228 }
229 return false;
230}
231
232bool SDL_HasClipboardData(const char *mime_type)
233{
234 SDL_VideoDevice *_this = SDL_GetVideoDevice();
235
236 if (!_this) {
237 SDL_UninitializedVideo();
238 return false;
239 }
240
241 if (!mime_type) {
242 SDL_InvalidParamError("mime_type");
243 return false;
244 }
245
246 if (_this->HasClipboardData) {
247 return _this->HasClipboardData(_this, mime_type);
248 } else if (_this->HasClipboardText && SDL_IsTextMimeType(mime_type)) {
249 return _this->HasClipboardText(_this);
250 } else {
251 return SDL_HasInternalClipboardData(_this, mime_type);
252 }
253}
254
255char **SDL_CopyClipboardMimeTypes(const char **clipboard_mime_types, size_t num_mime_types, bool temporary)
256{
257 size_t allocSize = sizeof(char *);
258 for (size_t i = 0; i < num_mime_types; i++) {
259 allocSize += sizeof(char *) + SDL_strlen(clipboard_mime_types[i]) + 1;
260 }
261
262 char *ret;
263 if (temporary)
264 ret = (char *)SDL_AllocateTemporaryMemory(allocSize);
265 else
266 ret = (char *)SDL_malloc(allocSize);
267 if (!ret) {
268 return NULL;
269 }
270
271 char **result = (char **)ret;
272 ret += sizeof(char *) * (num_mime_types + 1);
273
274 for (size_t i = 0; i < num_mime_types; i++) {
275 result[i] = ret;
276
277 const char *mime_type = clipboard_mime_types[i];
278 // Copy the whole string including the terminating null char
279 char c;
280 do {
281 c = *ret++ = *mime_type++;
282 } while (c != '\0');
283 }
284 result[num_mime_types] = NULL;
285
286 return result;
287
288}
289
290char **SDL_GetClipboardMimeTypes(size_t *num_mime_types)
291{
292 SDL_VideoDevice *_this = SDL_GetVideoDevice();
293
294 if (num_mime_types) {
295 *num_mime_types = 0;
296 }
297
298 if (!_this) {
299 SDL_UninitializedVideo();
300 return NULL;
301 }
302
303 if (num_mime_types) {
304 *num_mime_types = _this->num_clipboard_mime_types;
305 }
306 return SDL_CopyClipboardMimeTypes((const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types, false);
307}
308
309// Clipboard text
310
311bool SDL_IsTextMimeType(const char *mime_type)
312{
313 return (SDL_strncmp(mime_type, "text", 4) == 0);
314}
315
316static const char **SDL_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types)
317{
318 if (_this->GetTextMimeTypes) {
319 return _this->GetTextMimeTypes(_this, num_mime_types);
320 } else {
321 static const char *text_mime_types[] = {
322 "text/plain;charset=utf-8"
323 };
324
325 *num_mime_types = SDL_arraysize(text_mime_types);
326 return text_mime_types;
327 }
328}
329
330const void * SDLCALL SDL_ClipboardTextCallback(void *userdata, const char *mime_type, size_t *size)
331{
332 char *text = (char *)userdata;
333 if (text) {
334 *size = SDL_strlen(text);
335 } else {
336 *size = 0;
337 }
338 return text;
339}
340
341bool SDL_SetClipboardText(const char *text)
342{
343 SDL_VideoDevice *_this = SDL_GetVideoDevice();
344 size_t num_mime_types;
345 const char **text_mime_types;
346
347 if (!_this) {
348 return SDL_UninitializedVideo();
349 }
350
351 if (text && *text) {
352 text_mime_types = SDL_GetTextMimeTypes(_this, &num_mime_types);
353
354 return SDL_SetClipboardData(SDL_ClipboardTextCallback, SDL_free, SDL_strdup(text), text_mime_types, num_mime_types);
355 }
356 return SDL_ClearClipboardData();
357}
358
359char *SDL_GetClipboardText(void)
360{
361 SDL_VideoDevice *_this = SDL_GetVideoDevice();
362 size_t i, num_mime_types;
363 const char **text_mime_types;
364 size_t length;
365 char *text = NULL;
366
367 if (!_this) {
368 SDL_UninitializedVideo();
369 return SDL_strdup("");
370 }
371
372 text_mime_types = SDL_GetTextMimeTypes(_this, &num_mime_types);
373 for (i = 0; i < num_mime_types; ++i) {
374 void *clipdata = SDL_GetClipboardData(text_mime_types[i], &length);
375 if (clipdata) {
376 text = (char *)clipdata;
377 break;
378 }
379 }
380
381 if (!text) {
382 text = SDL_strdup("");
383 }
384 return text;
385}
386
387bool SDL_HasClipboardText(void)
388{
389 SDL_VideoDevice *_this = SDL_GetVideoDevice();
390 size_t i, num_mime_types;
391 const char **text_mime_types;
392
393 if (!_this) {
394 return SDL_UninitializedVideo();
395 }
396
397 text_mime_types = SDL_GetTextMimeTypes(_this, &num_mime_types);
398 for (i = 0; i < num_mime_types; ++i) {
399 if (SDL_HasClipboardData(text_mime_types[i])) {
400 return true;
401 }
402 }
403 return false;
404}
405
406// Primary selection text
407
408bool SDL_SetPrimarySelectionText(const char *text)
409{
410 SDL_VideoDevice *_this = SDL_GetVideoDevice();
411
412 if (!_this) {
413 return SDL_UninitializedVideo();
414 }
415
416 if (!text) {
417 text = "";
418 }
419 if (_this->SetPrimarySelectionText) {
420 if (!_this->SetPrimarySelectionText(_this, text)) {
421 return false;
422 }
423 } else {
424 SDL_free(_this->primary_selection_text);
425 _this->primary_selection_text = SDL_strdup(text);
426 }
427
428 char **mime_types = SDL_CopyClipboardMimeTypes((const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types, true);
429 if (!mime_types)
430 return SDL_SetError("unable to copy current mime types");
431
432 SDL_SendClipboardUpdate(true, mime_types, _this->num_clipboard_mime_types);
433 return true;
434}
435
436char *SDL_GetPrimarySelectionText(void)
437{
438 SDL_VideoDevice *_this = SDL_GetVideoDevice();
439
440 if (!_this) {
441 SDL_UninitializedVideo();
442 return SDL_strdup("");
443 }
444
445 if (_this->GetPrimarySelectionText) {
446 return _this->GetPrimarySelectionText(_this);
447 } else {
448 const char *text = _this->primary_selection_text;
449 if (!text) {
450 text = "";
451 }
452 return SDL_strdup(text);
453 }
454}
455
456bool SDL_HasPrimarySelectionText(void)
457{
458 SDL_VideoDevice *_this = SDL_GetVideoDevice();
459
460 if (!_this) {
461 return SDL_UninitializedVideo();
462 }
463
464 if (_this->HasPrimarySelectionText) {
465 return _this->HasPrimarySelectionText(_this);
466 } else {
467 if (_this->primary_selection_text && _this->primary_selection_text[0] != '\0') {
468 return true;
469 } else {
470 return false;
471 }
472 }
473}
474
475