Atlas - SDL_windowsclipboard.c
Home / ext / SDL / src / video / windows Lines: 3 | Size: 18380 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#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) 24 25#include "SDL_windowsvideo.h" 26#include "SDL_windowswindow.h" 27#include "../SDL_clipboard_c.h" 28#include "../../events/SDL_events_c.h" 29#include "../../events/SDL_clipboardevents_c.h" 30 31#define BFT_BITMAP 0x4d42 // 'BM' 32 33// Assume we can directly read and write BMP fields without byte swapping 34SDL_COMPILE_TIME_ASSERT(verify_byte_order, SDL_BYTEORDER == SDL_LIL_ENDIAN); 35 36static UINT GetClipboardFormatPNG() 37{ 38 static UINT format; 39 40 if (!format) { 41 format = RegisterClipboardFormat(TEXT("PNG")); 42 } 43 return format; 44} 45 46static BOOL WIN_OpenClipboard(SDL_VideoDevice *_this) 47{ 48 // Retry to open the clipboard in case another application has it open 49 const int MAX_ATTEMPTS = 3; 50 int attempt; 51 HWND hwnd = NULL; 52 53 if (_this->windows) { 54 hwnd = _this->windows->internal->hwnd; 55 } 56 for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { 57 if (OpenClipboard(hwnd)) { 58 return TRUE; 59 } 60 SDL_Delay(10); 61 } 62 return FALSE; 63} 64 65static void WIN_CloseClipboard(void) 66{ 67 CloseClipboard(); 68} 69 70static HANDLE WIN_ConvertBMPtoDIB(const void *bmp, size_t bmp_size, UINT *format) 71{ 72 HANDLE hMem = NULL; 73 74 if (bmp && bmp_size > sizeof(BITMAPFILEHEADER) && ((BITMAPFILEHEADER *)bmp)->bfType == BFT_BITMAP) { 75 BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp; 76 BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)((Uint8 *)bmp + sizeof(BITMAPFILEHEADER)); 77 size_t bih_size = pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD); 78 size_t pixels_size = pbih->biSizeImage; 79 80 if (pbih->biSize >= sizeof(BITMAPV5HEADER)) { 81 *format = CF_DIBV5; 82 } else { 83 *format = CF_DIB; 84 } 85 86 if (pbfh->bfOffBits >= (sizeof(BITMAPFILEHEADER) + bih_size) && 87 (pbfh->bfOffBits + pixels_size) <= bmp_size) { 88 const Uint8 *pixels = (const Uint8 *)bmp + pbfh->bfOffBits; 89 size_t dib_size = bih_size + pixels_size; 90 hMem = GlobalAlloc(GMEM_MOVEABLE, dib_size); 91 if (hMem) { 92 LPVOID dst = GlobalLock(hMem); 93 if (dst) { 94 SDL_memcpy(dst, pbih, bih_size); 95 SDL_memcpy((Uint8 *)dst + bih_size, pixels, pixels_size); 96 GlobalUnlock(hMem); 97 } else { 98 WIN_SetError("GlobalLock()"); 99 GlobalFree(hMem); 100 hMem = NULL; 101 } 102 } else { 103 SDL_OutOfMemory(); 104 } 105 } else { 106 SDL_SetError("Invalid BMP data"); 107 } 108 } else { 109 SDL_SetError("Invalid BMP data"); 110 } 111 return hMem; 112} 113 114static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size) 115{ 116 void *bmp = NULL; 117 size_t mem_size = GlobalSize(hMem); 118 119 if (mem_size > sizeof(BITMAPINFOHEADER)) { 120 LPVOID dib = GlobalLock(hMem); 121 if (dib) { 122 BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)dib; 123 124 // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader#color-tables 125 size_t color_table_size; 126 switch (pbih->biCompression) { 127 case BI_RGB: 128 if (pbih->biBitCount <= 8) { 129 color_table_size = sizeof(RGBQUAD) * (pbih->biClrUsed == 0 ? 1 << pbih->biBitCount : pbih->biClrUsed); 130 } else { 131 color_table_size = 0; 132 } 133 break; 134 case BI_BITFIELDS: 135 color_table_size = 3 * sizeof(DWORD); 136 break; 137 case 6 /* BI_ALPHABITFIELDS */: 138 // https://learn.microsoft.com/en-us/previous-versions/windows/embedded/aa452885(v=msdn.10) 139 color_table_size = 4 * sizeof(DWORD); 140 break; 141 default: // FOURCC 142 color_table_size = sizeof(RGBQUAD) * pbih->biClrUsed; 143 } 144 145 size_t bih_size = pbih->biSize + color_table_size; 146 size_t dib_size = bih_size + pbih->biSizeImage; 147 if (dib_size <= mem_size) { 148 size_t bmp_size = sizeof(BITMAPFILEHEADER) + mem_size; 149 bmp = SDL_malloc(bmp_size); 150 if (bmp) { 151 BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp; 152 pbfh->bfType = BFT_BITMAP; 153 pbfh->bfSize = (DWORD)bmp_size; 154 pbfh->bfReserved1 = 0; 155 pbfh->bfReserved2 = 0; 156 pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + pbih->biSize + color_table_size); 157 SDL_memcpy((Uint8 *)bmp + sizeof(BITMAPFILEHEADER), dib, mem_size); 158 *size = bmp_size; 159 } 160 } else { 161 SDL_SetError("Invalid BMP data"); 162 } 163 GlobalUnlock(hMem); 164 } else { 165 WIN_SetError("GlobalLock()"); 166 } 167 } else { 168 SDL_SetError("Invalid BMP data"); 169 } 170 return bmp; 171} 172 173static bool WIN_SetClipboardImage(SDL_VideoDevice *_this, const char *mime_type) 174{ 175 UINT format = 0; 176 HANDLE hMem = NULL; 177 size_t clipboard_data_size; 178 const void *clipboard_data; 179 bool result = true; 180 181 clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &clipboard_data_size); 182 if (SDL_strcmp(mime_type, "image/bmp") == 0) { 183 hMem = WIN_ConvertBMPtoDIB(clipboard_data, clipboard_data_size, &format); 184 } else if (SDL_strcmp(mime_type, "image/png") == 0) { 185 format = GetClipboardFormatPNG(); 186 hMem = GlobalAlloc(GMEM_MOVEABLE, clipboard_data_size); 187 if (hMem) { 188 LPVOID dst = GlobalLock(hMem); 189 if (dst) { 190 SDL_memcpy(dst, clipboard_data, clipboard_data_size); 191 GlobalUnlock(hMem); 192 } else { 193 result = WIN_SetError("GlobalLock()"); 194 GlobalFree(hMem); 195 hMem = NULL; 196 } 197 } else { 198 result = SDL_OutOfMemory(); 199 } 200 } else { 201 result = SDL_SetError("Unknown image format"); 202 } 203 if (hMem) { 204 // Save the image to the clipboard 205 if (!SetClipboardData(format, hMem)) { 206 result = WIN_SetError("Couldn't set clipboard data"); 207 } 208 } else { 209 // WIN_ConvertBMPtoDIB() set the error 210 result = false; 211 } 212 return result; 213} 214 215static bool WIN_SetClipboardText(SDL_VideoDevice *_this, const char *mime_type) 216{ 217 HANDLE hMem; 218 size_t clipboard_data_size; 219 const void *clipboard_data; 220 bool result = true; 221 222 clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &clipboard_data_size); 223 if (clipboard_data && clipboard_data_size > 0) { 224 SIZE_T i, size; 225 LPWSTR tstr = (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)clipboard_data, clipboard_data_size); 226 if (!tstr) { 227 return SDL_SetError("Couldn't convert text from UTF-8"); 228 } 229 230 // Find out the size of the data 231 for (size = 0, i = 0; tstr[i]; ++i, ++size) { 232 if (tstr[i] == '\n' && (i == 0 || tstr[i - 1] != '\r')) { 233 // We're going to insert a carriage return 234 ++size; 235 } 236 } 237 size = (size + 1) * sizeof(*tstr); 238 239 // Save the data to the clipboard 240 hMem = GlobalAlloc(GMEM_MOVEABLE, size); 241 if (hMem) { 242 LPWSTR dst = (LPWSTR)GlobalLock(hMem); 243 if (dst) { 244 // Copy the text over, adding carriage returns as necessary 245 for (i = 0; tstr[i]; ++i) { 246 if (tstr[i] == '\n' && (i == 0 || tstr[i - 1] != '\r')) { 247 *dst++ = '\r'; 248 } 249 *dst++ = tstr[i]; 250 } 251 *dst = 0; 252 GlobalUnlock(hMem); 253 } 254 255 if (!SetClipboardData(CF_UNICODETEXT, hMem)) { 256 result = WIN_SetError("Couldn't set clipboard data"); 257 } 258 } else { 259 result = SDL_OutOfMemory(); 260 } 261 SDL_free(tstr); 262 } 263 return result; 264} 265 266bool WIN_SetClipboardData(SDL_VideoDevice *_this) 267{ 268 SDL_VideoData *data = _this->internal; 269 size_t i; 270 bool result = true; 271 272 /* I investigated delayed clipboard rendering, and at least with text and image 273 * formats you have to use an output window, not SDL_HelperWindow, and the system 274 * requests them being rendered immediately, so there isn't any benefit. 275 */ 276 277 if (WIN_OpenClipboard(_this)) { 278 EmptyClipboard(); 279 280 // Set the clipboard text 281 for (i = 0; i < _this->num_clipboard_mime_types; ++i) { 282 const char *mime_type = _this->clipboard_mime_types[i]; 283 284 if (SDL_IsTextMimeType(mime_type)) { 285 if (!WIN_SetClipboardText(_this, mime_type)) { 286 result = false; 287 } 288 // Only set the first clipboard text 289 break; 290 } 291 } 292 293 // Set the clipboard image 294 for (i = 0; i < _this->num_clipboard_mime_types; ++i) { 295 const char *mime_type = _this->clipboard_mime_types[i]; 296 297 if (SDL_strcmp(mime_type, "image/bmp") == 0 || 298 SDL_strcmp(mime_type, "image/png") == 0) { 299 if (!WIN_SetClipboardImage(_this, mime_type)) { 300 result = false; 301 } 302 break; 303 } 304 } 305 306 data->clipboard_count = GetClipboardSequenceNumber(); 307 WIN_CloseClipboard(); 308 } else { 309 result = WIN_SetError("Couldn't open clipboard"); 310 } 311 return result; 312} 313 314void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size) 315{ 316 void *data = NULL; 317 318 if (SDL_IsTextMimeType(mime_type)) { 319 char *text = NULL; 320 321 if (IsClipboardFormatAvailable(CF_UNICODETEXT)) { 322 if (WIN_OpenClipboard(_this)) { 323 HANDLE hMem; 324 LPTSTR str; 325 326 hMem = GetClipboardData(CF_UNICODETEXT); 327 if (hMem) { 328 str = (LPTSTR)GlobalLock(hMem); 329 if (str) { 330 text = WIN_StringToUTF8W(str); 331 GlobalUnlock(hMem); 332 } else { 333 WIN_SetError("Couldn't lock clipboard data"); 334 } 335 } else { 336 WIN_SetError("Couldn't get clipboard data"); 337 } 338 WIN_CloseClipboard(); 339 } 340 } else if (IsClipboardFormatAvailable(CF_TEXT)) { 341 if (WIN_OpenClipboard(_this)) { 342 HANDLE hMem; 343 LPCSTR str; 344 345 hMem = GetClipboardData(CF_TEXT); 346 if (hMem) { 347 str = (LPCSTR)GlobalLock(hMem); 348 if (str) { 349 text = SDL_strdup(str); 350 GlobalUnlock(hMem); 351 } else { 352 WIN_SetError("Couldn't lock clipboard data"); 353 } 354 } else { 355 WIN_SetError("Couldn't get clipboard data"); 356 } 357 WIN_CloseClipboard(); 358 } 359 } 360 if (!text) { 361 text = SDL_strdup(""); 362 } 363 data = text; 364 *size = SDL_strlen(text); 365 366 } else if (SDL_strcmp(mime_type, "image/bmp") == 0) { 367 if (IsClipboardFormatAvailable(CF_DIBV5)) { 368 if (WIN_OpenClipboard(_this)) { 369 HANDLE hMem = GetClipboardData(CF_DIBV5); 370 if (hMem) { 371 data = WIN_ConvertDIBtoBMP(hMem, size); 372 } else { 373 WIN_SetError("Couldn't get clipboard data"); 374 } 375 WIN_CloseClipboard(); 376 } 377 } else if (IsClipboardFormatAvailable(CF_DIB)) { 378 if (WIN_OpenClipboard(_this)) { 379 HANDLE hMem = GetClipboardData(CF_DIB); 380 if (hMem) { 381 data = WIN_ConvertDIBtoBMP(hMem, size); 382 } else { 383 WIN_SetError("Couldn't get clipboard data"); 384 } 385 WIN_CloseClipboard(); 386 } 387 } 388 389 } else if (SDL_strcmp(mime_type, "image/png") == 0) { 390 if (IsClipboardFormatAvailable(GetClipboardFormatPNG())) { 391 if (WIN_OpenClipboard(_this)) { 392 HANDLE hMem = GetClipboardData(GetClipboardFormatPNG()); 393 if (hMem) { 394 size_t mem_size = GlobalSize(hMem); 395 void *mem = GlobalLock(hMem); 396 if (mem) { 397 data = SDL_malloc(mem_size); 398 if (data) { 399 SDL_memcpy(data, mem, mem_size); 400 *size = mem_size; 401 } 402 GlobalUnlock(hMem); 403 } else { 404 WIN_SetError("Couldn't lock clipboard data"); 405 } 406 } else { 407 WIN_SetError("Couldn't get clipboard data"); 408 } 409 WIN_CloseClipboard(); 410 } 411 } 412 413 } else { 414 data = SDL_GetInternalClipboardData(_this, mime_type, size); 415 } 416 return data; 417} 418 419bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type) 420{ 421 if (SDL_IsTextMimeType(mime_type)) { 422 if (IsClipboardFormatAvailable(CF_UNICODETEXT) || IsClipboardFormatAvailable(CF_TEXT)) { 423 return true; 424 } 425 } else if (SDL_strcmp(mime_type, "image/bmp") == 0) { 426 if (IsClipboardFormatAvailable(CF_DIBV5) || IsClipboardFormatAvailable(CF_DIB)) { 427 return true; 428 } 429 } else if (SDL_strcmp(mime_type, "image/png") == 0) { 430 if (IsClipboardFormatAvailable(GetClipboardFormatPNG())) { 431 return true; 432 } 433 } 434 return SDL_HasInternalClipboardData(_this, mime_type); 435} 436 437static int GetClipboardFormatMimeType(UINT format, char *name) 438{ 439 const char *mime_type = NULL; 440 441 switch (format) { 442 case CF_TEXT: 443 mime_type = "text/plain"; 444 break; 445 case CF_UNICODETEXT: 446 mime_type = "text/plain;charset=utf-8"; 447 break; 448 case CF_DIB: 449 case CF_DIBV5: 450 mime_type = "image/bmp"; 451 break; 452 default: 453 if (format == GetClipboardFormatPNG()) { 454 mime_type = "image/png"; 455 } 456 break; 457 } 458 if (mime_type) { 459 size_t len = SDL_strlen(mime_type) + 1; 460 if (name) { 461 SDL_memcpy(name, mime_type, len); 462 } 463 return (int)len; 464 } 465 return 0; 466} 467 468static char **GetMimeTypes(int *pnformats) 469{ 470 char **new_mime_types = NULL; 471 472 *pnformats = 0; 473 474 if (WIN_OpenClipboard(SDL_GetVideoDevice())) { 475 int nformats = 0; 476 UINT format = 0; 477 int formatsSz = 0; 478 bool have_image_bmp = false; 479 for ( ; ; ) { 480 format = EnumClipboardFormats(format); 481 if (!format) { 482 break; 483 } 484 485#ifdef DEBUG_CLIPBOARD 486 char name[128] = { 0 }; 487 GetClipboardFormatNameA(format, name, sizeof(name)); 488 SDL_Log("Clipboard format: %d (0x%x), '%s'", format, format, name); 489#endif 490 491 if (format == CF_DIB || format == CF_DIBV5) { 492 if (have_image_bmp) { 493 // We have already registered this format 494 continue; 495 } 496 have_image_bmp = true; 497 } 498 499 int len = GetClipboardFormatMimeType(format, NULL); 500 if (len > 0) { 501 ++nformats; 502 formatsSz += len; 503 } 504 } 505 506 have_image_bmp = false; 507 new_mime_types = SDL_AllocateTemporaryMemory((nformats + 1) * sizeof(char *) + formatsSz); 508 if (new_mime_types) { 509 format = 0; 510 char *strPtr = (char *)(new_mime_types + nformats + 1); 511 int i = 0; 512 for ( ; ; ) { 513 format = EnumClipboardFormats(format); 514 if (!format) { 515 break; 516 } 517 518 if (format == CF_DIB || format == CF_DIBV5) { 519 if (have_image_bmp) { 520 // We have already registered this format 521 continue; 522 } 523 have_image_bmp = true; 524 } 525 526 int len = GetClipboardFormatMimeType(format, strPtr); 527 if (len > 0) { 528 new_mime_types[i++] = strPtr; 529 strPtr += len; 530 } 531 } 532 533 new_mime_types[nformats] = NULL; 534 *pnformats = nformats; 535 } 536 WIN_CloseClipboard(); 537 } 538 return new_mime_types; 539} 540 541void WIN_CheckClipboardUpdate(struct SDL_VideoData *data) 542{ 543 DWORD count = GetClipboardSequenceNumber(); 544 if (count != data->clipboard_count) { 545 if (count) { 546 int nformats = 0; 547 char **new_mime_types = GetMimeTypes(&nformats); 548 if (new_mime_types) { 549 SDL_SendClipboardUpdate(false, new_mime_types, nformats); 550 } 551 } 552 data->clipboard_count = count; 553 } 554} 555 556#endif // SDL_VIDEO_DRIVER_WINDOWS 557[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.