Added support for CF_DIBV5 and PNG clipboard formats on Windows

This commit is contained in:
Sam Lantinga
2025-10-10 16:53:13 -07:00
parent 9d3dd8f001
commit 3fcac8cc44

View File

@@ -28,19 +28,21 @@
#include "../../events/SDL_events_c.h" #include "../../events/SDL_events_c.h"
#include "../../events/SDL_clipboardevents_c.h" #include "../../events/SDL_clipboardevents_c.h"
#ifdef UNICODE
#define TEXT_FORMAT CF_UNICODETEXT
#else
#define TEXT_FORMAT CF_TEXT
#endif
#define IMAGE_FORMAT CF_DIB
#define IMAGE_MIME_TYPE "image/bmp"
#define BFT_BITMAP 0x4d42 // 'BM' #define BFT_BITMAP 0x4d42 // 'BM'
// Assume we can directly read and write BMP fields without byte swapping // Assume we can directly read and write BMP fields without byte swapping
SDL_COMPILE_TIME_ASSERT(verify_byte_order, SDL_BYTEORDER == SDL_LIL_ENDIAN); SDL_COMPILE_TIME_ASSERT(verify_byte_order, SDL_BYTEORDER == SDL_LIL_ENDIAN);
static UINT GetClipboardFormatPNG()
{
static UINT format;
if (!format) {
format = RegisterClipboardFormat(TEXT("PNG"));
}
return format;
}
static BOOL WIN_OpenClipboard(SDL_VideoDevice *_this) static BOOL WIN_OpenClipboard(SDL_VideoDevice *_this)
{ {
// Retry to open the clipboard in case another application has it open // Retry to open the clipboard in case another application has it open
@@ -65,7 +67,7 @@ static void WIN_CloseClipboard(void)
CloseClipboard(); CloseClipboard();
} }
static HANDLE WIN_ConvertBMPtoDIB(const void *bmp, size_t bmp_size) static HANDLE WIN_ConvertBMPtoDIB(const void *bmp, size_t bmp_size, UINT *format)
{ {
HANDLE hMem = NULL; HANDLE hMem = NULL;
@@ -75,6 +77,12 @@ static HANDLE WIN_ConvertBMPtoDIB(const void *bmp, size_t bmp_size)
size_t bih_size = pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD); size_t bih_size = pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);
size_t pixels_size = pbih->biSizeImage; size_t pixels_size = pbih->biSizeImage;
if (pbih->biSize >= sizeof(BITMAPV5HEADER)) {
*format = CF_DIBV5;
} else {
*format = CF_DIB;
}
if (pbfh->bfOffBits >= (sizeof(BITMAPFILEHEADER) + bih_size) && if (pbfh->bfOffBits >= (sizeof(BITMAPFILEHEADER) + bih_size) &&
(pbfh->bfOffBits + pixels_size) <= bmp_size) { (pbfh->bfOffBits + pixels_size) <= bmp_size) {
const Uint8 *pixels = (const Uint8 *)bmp + pbfh->bfOffBits; const Uint8 *pixels = (const Uint8 *)bmp + pbfh->bfOffBits;
@@ -146,7 +154,7 @@ static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size)
pbfh->bfReserved1 = 0; pbfh->bfReserved1 = 0;
pbfh->bfReserved2 = 0; pbfh->bfReserved2 = 0;
pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + pbih->biSize + color_table_size); pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + pbih->biSize + color_table_size);
SDL_memcpy((Uint8 *)bmp + sizeof(BITMAPFILEHEADER), dib, dib_size); SDL_memcpy((Uint8 *)bmp + sizeof(BITMAPFILEHEADER), dib, mem_size);
*size = bmp_size; *size = bmp_size;
} }
} else { } else {
@@ -162,18 +170,39 @@ static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size)
return bmp; return bmp;
} }
static bool WIN_SetClipboardImage(SDL_VideoDevice *_this) static bool WIN_SetClipboardImage(SDL_VideoDevice *_this, const char *mime_type)
{ {
HANDLE hMem; UINT format = 0;
HANDLE hMem = NULL;
size_t clipboard_data_size; size_t clipboard_data_size;
const void *clipboard_data; const void *clipboard_data;
bool result = true; bool result = true;
clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, IMAGE_MIME_TYPE, &clipboard_data_size); clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &clipboard_data_size);
hMem = WIN_ConvertBMPtoDIB(clipboard_data, clipboard_data_size); if (SDL_strcmp(mime_type, "image/bmp") == 0) {
hMem = WIN_ConvertBMPtoDIB(clipboard_data, clipboard_data_size, &format);
} else if (SDL_strcmp(mime_type, "image/png") == 0) {
format = GetClipboardFormatPNG();
hMem = GlobalAlloc(GMEM_MOVEABLE, clipboard_data_size);
if (hMem) {
LPVOID dst = GlobalLock(hMem);
if (dst) {
SDL_memcpy(dst, clipboard_data, clipboard_data_size);
GlobalUnlock(hMem);
} else {
result = WIN_SetError("GlobalLock()");
GlobalFree(hMem);
hMem = NULL;
}
} else {
result = SDL_OutOfMemory();
}
} else {
result = SDL_SetError("Unknown image format");
}
if (hMem) { if (hMem) {
// Save the image to the clipboard // Save the image to the clipboard
if (!SetClipboardData(IMAGE_FORMAT, hMem)) { if (!SetClipboardData(format, hMem)) {
result = WIN_SetError("Couldn't set clipboard data"); result = WIN_SetError("Couldn't set clipboard data");
} }
} else { } else {
@@ -223,7 +252,7 @@ static bool WIN_SetClipboardText(SDL_VideoDevice *_this, const char *mime_type)
GlobalUnlock(hMem); GlobalUnlock(hMem);
} }
if (!SetClipboardData(TEXT_FORMAT, hMem)) { if (!SetClipboardData(CF_UNICODETEXT, hMem)) {
result = WIN_SetError("Couldn't set clipboard data"); result = WIN_SetError("Couldn't set clipboard data");
} }
} else { } else {
@@ -265,8 +294,9 @@ bool WIN_SetClipboardData(SDL_VideoDevice *_this)
for (i = 0; i < _this->num_clipboard_mime_types; ++i) { for (i = 0; i < _this->num_clipboard_mime_types; ++i) {
const char *mime_type = _this->clipboard_mime_types[i]; const char *mime_type = _this->clipboard_mime_types[i];
if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) { if (SDL_strcmp(mime_type, "image/bmp") == 0 ||
if (!WIN_SetClipboardImage(_this)) { SDL_strcmp(mime_type, "image/png") == 0) {
if (!WIN_SetClipboardImage(_this, mime_type)) {
result = false; result = false;
} }
break; break;
@@ -288,16 +318,35 @@ void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t
if (SDL_IsTextMimeType(mime_type)) { if (SDL_IsTextMimeType(mime_type)) {
char *text = NULL; char *text = NULL;
if (IsClipboardFormatAvailable(TEXT_FORMAT)) { if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
if (WIN_OpenClipboard(_this)) { if (WIN_OpenClipboard(_this)) {
HANDLE hMem; HANDLE hMem;
LPTSTR tstr; LPTSTR str;
hMem = GetClipboardData(TEXT_FORMAT); hMem = GetClipboardData(CF_UNICODETEXT);
if (hMem) { if (hMem) {
tstr = (LPTSTR)GlobalLock(hMem); str = (LPTSTR)GlobalLock(hMem);
if (tstr) { if (str) {
text = WIN_StringToUTF8(tstr); text = WIN_StringToUTF8W(str);
GlobalUnlock(hMem);
} else {
WIN_SetError("Couldn't lock clipboard data");
}
} else {
WIN_SetError("Couldn't get clipboard data");
}
WIN_CloseClipboard();
}
} else if (IsClipboardFormatAvailable(CF_TEXT)) {
if (WIN_OpenClipboard(_this)) {
HANDLE hMem;
LPCSTR str;
hMem = GetClipboardData(CF_TEXT);
if (hMem) {
str = (LPCSTR)GlobalLock(hMem);
if (str) {
text = SDL_strdup(str);
GlobalUnlock(hMem); GlobalUnlock(hMem);
} else { } else {
WIN_SetError("Couldn't lock clipboard data"); WIN_SetError("Couldn't lock clipboard data");
@@ -314,12 +363,20 @@ void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t
data = text; data = text;
*size = SDL_strlen(text); *size = SDL_strlen(text);
} else if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) { } else if (SDL_strcmp(mime_type, "image/bmp") == 0) {
if (IsClipboardFormatAvailable(IMAGE_FORMAT)) { if (IsClipboardFormatAvailable(CF_DIBV5)) {
if (WIN_OpenClipboard(_this)) { if (WIN_OpenClipboard(_this)) {
HANDLE hMem; HANDLE hMem = GetClipboardData(CF_DIBV5);
if (hMem) {
hMem = GetClipboardData(IMAGE_FORMAT); data = WIN_ConvertDIBtoBMP(hMem, size);
} else {
WIN_SetError("Couldn't get clipboard data");
}
WIN_CloseClipboard();
}
} else if (IsClipboardFormatAvailable(CF_DIB)) {
if (WIN_OpenClipboard(_this)) {
HANDLE hMem = GetClipboardData(CF_DIB);
if (hMem) { if (hMem) {
data = WIN_ConvertDIBtoBMP(hMem, size); data = WIN_ConvertDIBtoBMP(hMem, size);
} else { } else {
@@ -328,6 +385,31 @@ void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t
WIN_CloseClipboard(); WIN_CloseClipboard();
} }
} }
} else if (SDL_strcmp(mime_type, "image/png") == 0) {
if (IsClipboardFormatAvailable(GetClipboardFormatPNG())) {
if (WIN_OpenClipboard(_this)) {
HANDLE hMem = GetClipboardData(GetClipboardFormatPNG());
if (hMem) {
size_t mem_size = GlobalSize(hMem);
void *mem = GlobalLock(hMem);
if (mem) {
data = SDL_malloc(mem_size);
if (data) {
SDL_memcpy(data, mem, mem_size);
*size = mem_size;
}
GlobalUnlock(hMem);
} else {
WIN_SetError("Couldn't lock clipboard data");
}
} else {
WIN_SetError("Couldn't get clipboard data");
}
WIN_CloseClipboard();
}
}
} else { } else {
data = SDL_GetInternalClipboardData(_this, mime_type, size); data = SDL_GetInternalClipboardData(_this, mime_type, size);
} }
@@ -337,41 +419,49 @@ void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t
bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type) bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
{ {
if (SDL_IsTextMimeType(mime_type)) { if (SDL_IsTextMimeType(mime_type)) {
if (IsClipboardFormatAvailable(TEXT_FORMAT)) { if (IsClipboardFormatAvailable(CF_UNICODETEXT) || IsClipboardFormatAvailable(CF_TEXT)) {
return true; return true;
} }
} else if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) { } else if (SDL_strcmp(mime_type, "image/bmp") == 0) {
if (IsClipboardFormatAvailable(IMAGE_FORMAT)) { if (IsClipboardFormatAvailable(CF_DIBV5) || IsClipboardFormatAvailable(CF_DIB)) {
return true; return true;
} }
} else { } else if (SDL_strcmp(mime_type, "image/png") == 0) {
if (SDL_HasInternalClipboardData(_this, mime_type)) { if (IsClipboardFormatAvailable(GetClipboardFormatPNG())) {
return true; return true;
} }
} }
return false; return SDL_HasInternalClipboardData(_this, mime_type);
} }
static int GetClipboardFormatMimeType(UINT format, char *name) static int GetClipboardFormatMimeType(UINT format, char *name)
{ {
static struct const char *mime_type = NULL;
{
UINT format;
const char *mime_type;
} mime_types[] = {
{ TEXT_FORMAT, "text/plain;charset=utf-8" },
{ IMAGE_FORMAT, IMAGE_MIME_TYPE },
};
for (int i = 0; i < SDL_arraysize(mime_types); ++i) { switch (format) {
if (format == mime_types[i].format) { case CF_TEXT:
size_t len = SDL_strlen(mime_types[i].mime_type) + 1; mime_type = "text/plain";
break;
case CF_UNICODETEXT:
mime_type = "text/plain;charset=utf-8";
break;
case CF_DIB:
case CF_DIBV5:
mime_type = "image/bmp";
break;
default:
if (format == GetClipboardFormatPNG()) {
mime_type = "image/png";
}
break;
}
if (mime_type) {
size_t len = SDL_strlen(mime_type) + 1;
if (name) { if (name) {
SDL_memcpy(name, mime_types[i].mime_type, len); SDL_memcpy(name, mime_type, len);
} }
return (int)len; return (int)len;
} }
}
return 0; return 0;
} }
@@ -385,12 +475,27 @@ static char **GetMimeTypes(int *pnformats)
int nformats = 0; int nformats = 0;
UINT format = 0; UINT format = 0;
int formatsSz = 0; int formatsSz = 0;
bool have_image_bmp = false;
for ( ; ; ) { for ( ; ; ) {
format = EnumClipboardFormats(format); format = EnumClipboardFormats(format);
if (!format) { if (!format) {
break; break;
} }
#ifdef DEBUG_CLIPBOARD
char name[128] = { 0 };
GetClipboardFormatNameA(format, name, sizeof(name));
SDL_Log("Clipboard format: %d (0x%x), '%s'", format, format, name);
#endif
if (format == CF_DIB || format == CF_DIBV5) {
if (have_image_bmp) {
// We have already registered this format
continue;
}
have_image_bmp = true;
}
int len = GetClipboardFormatMimeType(format, NULL); int len = GetClipboardFormatMimeType(format, NULL);
if (len > 0) { if (len > 0) {
++nformats; ++nformats;
@@ -398,6 +503,7 @@ static char **GetMimeTypes(int *pnformats)
} }
} }
have_image_bmp = false;
new_mime_types = SDL_AllocateTemporaryMemory((nformats + 1) * sizeof(char *) + formatsSz); new_mime_types = SDL_AllocateTemporaryMemory((nformats + 1) * sizeof(char *) + formatsSz);
if (new_mime_types) { if (new_mime_types) {
format = 0; format = 0;
@@ -409,6 +515,14 @@ static char **GetMimeTypes(int *pnformats)
break; break;
} }
if (format == CF_DIB || format == CF_DIBV5) {
if (have_image_bmp) {
// We have already registered this format
continue;
}
have_image_bmp = true;
}
int len = GetClipboardFormatMimeType(format, strPtr); int len = GetClipboardFormatMimeType(format, strPtr);
if (len > 0) { if (len > 0) {
new_mime_types[i++] = strPtr; new_mime_types[i++] = strPtr;