From 443868143ce3d925f575c66e2218edcd8bc884d7 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 4 Jul 2023 11:09:43 -0700
Subject: [PATCH] Added support for clipboard data on Windows
The only supported image format is image/bmp
---
src/video/windows/SDL_windowsclipboard.c | 228 ++++++++++++++++++++---
src/video/windows/SDL_windowsclipboard.h | 3 +
src/video/windows/SDL_windowsvideo.c | 3 +
3 files changed, 204 insertions(+), 30 deletions(-)
diff --git a/src/video/windows/SDL_windowsclipboard.c b/src/video/windows/SDL_windowsclipboard.c
index 7af109c44fa9..2ecb081f8207 100644
--- a/src/video/windows/SDL_windowsclipboard.c
+++ b/src/video/windows/SDL_windowsclipboard.c
@@ -31,17 +31,190 @@
#else
#define TEXT_FORMAT CF_TEXT
#endif
+#define IMAGE_FORMAT CF_DIB
+#define IMAGE_MIME_TYPE "image/bmp"
-/* Get any application owned window handle for clipboard association */
-static HWND GetWindowHandle(SDL_VideoDevice *_this)
+/* Assume we can directly read and write BMP fields without byte swapping */
+SDL_COMPILE_TIME_ASSERT(verify_byte_order, SDL_BYTEORDER == SDL_LIL_ENDIAN);
+
+static const char bmp_magic[2] = { 'B', 'M' };
+
+static BOOL WIN_OpenClipboard(SDL_VideoDevice *_this)
+{
+ /* Retry to open the clipboard in case another application has it open */
+ const int MAX_ATTEMPTS = 3;
+ int attempt;
+ HWND hwnd = NULL;
+
+ if (_this->windows) {
+ hwnd = _this->windows->driverdata->hwnd;
+ }
+ for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
+ if (OpenClipboard(hwnd)) {
+ return TRUE;
+ }
+ SDL_Delay(10);
+ }
+ return FALSE;
+}
+
+static void WIN_CloseClipboard(void)
+{
+ CloseClipboard();
+}
+
+static HANDLE WIN_ConvertBMPtoDIB(const void *bmp, size_t bmp_size)
+{
+ HANDLE hMem = NULL;
+
+ if (bmp && bmp_size > sizeof(BITMAPFILEHEADER) && SDL_memcmp(bmp, bmp_magic, sizeof(bmp_magic)) == 0) {
+ BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp;
+ BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)((Uint8 *)bmp + sizeof(BITMAPFILEHEADER));
+ size_t bih_size = pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);
+ size_t pixels_size = pbih->biSizeImage;
+
+ if (pbfh->bfOffBits >= (sizeof(BITMAPFILEHEADER) + bih_size) &&
+ (pbfh->bfOffBits + pixels_size) <= bmp_size) {
+ const Uint8 *pixels = (const Uint8 *)bmp + pbfh->bfOffBits;
+ size_t dib_size = bih_size + pixels_size;
+ hMem = GlobalAlloc(GMEM_MOVEABLE, dib_size);
+ if (hMem) {
+ LPVOID dst = GlobalLock(hMem);
+ if (dst) {
+ SDL_memcpy(dst, pbih, bih_size);
+ SDL_memcpy((Uint8 *)dst + bih_size, pixels, pixels_size);
+ GlobalUnlock(hMem);
+ } else {
+ WIN_SetError("GlobalLock()");
+ GlobalFree(hMem);
+ hMem = NULL;
+ }
+ } else {
+ SDL_OutOfMemory();
+ }
+ } else {
+ SDL_SetError("Invalid BMP data");
+ }
+ } else {
+ SDL_SetError("Invalid BMP data");
+ }
+ return hMem;
+}
+
+static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size)
+{
+ void *bmp = NULL;
+ size_t mem_size = GlobalSize(hMem);
+
+ if (mem_size > sizeof(BITMAPINFOHEADER)) {
+ LPVOID dib = GlobalLock(hMem);
+ if (dib) {
+ BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)dib;
+ size_t bih_size = pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);
+ size_t dib_size = bih_size + pbih->biSizeImage;
+ if (dib_size <= mem_size) {
+ size_t bmp_size = sizeof(BITMAPFILEHEADER) + dib_size;
+ bmp = SDL_malloc(bmp_size);
+ if (bmp) {
+ BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp;
+ pbfh->bfType = 0x4d42; /* bmp_magic */
+ pbfh->bfSize = (DWORD)bmp_size;
+ pbfh->bfReserved1 = 0;
+ pbfh->bfReserved2 = 0;
+ pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + bih_size);
+ SDL_memcpy((Uint8 *)bmp + sizeof(BITMAPFILEHEADER), dib, dib_size);
+ *size = bmp_size;
+ } else {
+ SDL_OutOfMemory();
+ }
+ } else {
+ SDL_SetError("Invalid BMP data");
+ }
+ GlobalUnlock(hMem);
+ } else {
+ WIN_SetError("GlobalLock()");
+ }
+ } else {
+ SDL_SetError("Invalid BMP data");
+ }
+ return bmp;
+}
+
+static int WIN_SetClipboardImage(SDL_VideoDevice *_this)
+{
+ SDL_VideoData *data = _this->driverdata;
+ int result = 0;
+
+ if (WIN_OpenClipboard(_this)) {
+ HANDLE hMem;
+ size_t clipboard_data_size;
+ const void *clipboard_data;
+
+ clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, IMAGE_MIME_TYPE, &clipboard_data_size);
+ hMem = WIN_ConvertBMPtoDIB(clipboard_data, clipboard_data_size);
+ if (hMem) {
+ /* Save the image to the clipboard */
+ EmptyClipboard();
+ if (!SetClipboardData(IMAGE_FORMAT, hMem)) {
+ result = WIN_SetError("Couldn't set clipboard data");
+ }
+ data->clipboard_count = GetClipboardSequenceNumber();
+ } else {
+ /* WIN_ConvertBMPtoDIB() set the error */
+ result = -1;
+ }
+
+ WIN_CloseClipboard();
+ } else {
+ result = WIN_SetError("Couldn't open clipboard");
+ }
+ return result;
+}
+
+int WIN_SetClipboardData(SDL_VideoDevice *_this)
+{
+ size_t i;
+ int result = 0;
+
+ /* Right now only BMP data is supported for interchange with other applications */
+ for (i = 0; i < _this->num_clipboard_mime_types; ++i) {
+ if (SDL_strcmp(_this->clipboard_mime_types[i], IMAGE_MIME_TYPE) == 0) {
+ result = WIN_SetClipboardImage(_this);
+ }
+ }
+ return result;
+}
+
+void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size)
{
- SDL_Window *window;
+ void *data = NULL;
+
+ if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) {
+ if (IsClipboardFormatAvailable(IMAGE_FORMAT)) {
+ if (WIN_OpenClipboard(_this)) {
+ HANDLE hMem;
+
+ hMem = GetClipboardData(IMAGE_FORMAT);
+ if (hMem) {
+ data = WIN_ConvertDIBtoBMP(hMem, size);
+ } else {
+ WIN_SetError("Couldn't get clipboard data");
+ }
+ WIN_CloseClipboard();
+ }
+ }
+ }
+ return data;
+}
- window = _this->windows;
- if (window) {
- return window->driverdata->hwnd;
+SDL_bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
+{
+ if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) {
+ if (IsClipboardFormatAvailable(IMAGE_FORMAT)) {
+ return SDL_TRUE;
+ }
}
- return NULL;
+ return SDL_FALSE;
}
int WIN_SetClipboardText(SDL_VideoDevice *_this, const char *text)
@@ -49,7 +222,7 @@ int WIN_SetClipboardText(SDL_VideoDevice *_this, const char *text)
SDL_VideoData *data = _this->driverdata;
int result = 0;
- if (OpenClipboard(GetWindowHandle(_this))) {
+ if (WIN_OpenClipboard(_this)) {
HANDLE hMem;
LPTSTR tstr;
SIZE_T i, size;
@@ -90,10 +263,12 @@ int WIN_SetClipboardText(SDL_VideoDevice *_this, const char *text)
result = WIN_SetError("Couldn't set clipboard data");
}
data->clipboard_count = GetClipboardSequenceNumber();
+ } else {
+ result = SDL_OutOfMemory();
}
SDL_free(tstr);
- CloseClipboard();
+ WIN_CloseClipboard();
} else {
result = WIN_SetError("Couldn't open clipboard");
}
@@ -105,27 +280,23 @@ char *WIN_GetClipboardText(SDL_VideoDevice *_this)
char *text = NULL;
if (IsClipboardFormatAvailable(TEXT_FORMAT)) {
- /* Retry to open the clipboard in case another application has it open */
- const int MAX_ATTEMPTS = 3;
- int attempt;
-
- for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
- if (OpenClipboard(GetWindowHandle(_this))) {
- HANDLE hMem;
- LPTSTR tstr;
+ if (WIN_OpenClipboard(_this)) {
+ HANDLE hMem;
+ LPTSTR tstr;
- hMem = GetClipboardData(TEXT_FORMAT);
- if (hMem) {
- tstr = (LPTSTR)GlobalLock(hMem);
+ hMem = GetClipboardData(TEXT_FORMAT);
+ if (hMem) {
+ tstr = (LPTSTR)GlobalLock(hMem);
+ if (tstr) {
text = WIN_StringToUTF8(tstr);
GlobalUnlock(hMem);
} else {
- WIN_SetError("Couldn't get clipboard data");
+ WIN_SetError("Couldn't lock clipboard data");
}
- CloseClipboard();
- break;
+ } else {
+ WIN_SetError("Couldn't get clipboard data");
}
- SDL_Delay(10);
+ WIN_CloseClipboard();
}
}
if (text == NULL) {
@@ -136,13 +307,10 @@ char *WIN_GetClipboardText(SDL_VideoDevice *_this)
SDL_bool WIN_HasClipboardText(SDL_VideoDevice *_this)
{
- SDL_bool result = SDL_FALSE;
- char *text = WIN_GetClipboardText(_this);
- if (text) {
- result = text[0] != '\0' ? SDL_TRUE : SDL_FALSE;
- SDL_free(text);
+ if (IsClipboardFormatAvailable(TEXT_FORMAT)) {
+ return SDL_TRUE;
}
- return result;
+ return SDL_FALSE;
}
void WIN_CheckClipboardUpdate(struct SDL_VideoData *data)
diff --git a/src/video/windows/SDL_windowsclipboard.h b/src/video/windows/SDL_windowsclipboard.h
index fd1cc52290f7..beb068dae3e8 100644
--- a/src/video/windows/SDL_windowsclipboard.h
+++ b/src/video/windows/SDL_windowsclipboard.h
@@ -26,6 +26,9 @@
/* Forward declaration */
struct SDL_VideoData;
+int WIN_SetClipboardData(SDL_VideoDevice *_this);
+void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size);
+SDL_bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type);
extern int WIN_SetClipboardText(SDL_VideoDevice *_this, const char *text);
extern char *WIN_GetClipboardText(SDL_VideoDevice *_this);
extern SDL_bool WIN_HasClipboardText(SDL_VideoDevice *_this);
diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c
index b1617385b6b2..d8669bdc10eb 100644
--- a/src/video/windows/SDL_windowsvideo.c
+++ b/src/video/windows/SDL_windowsvideo.c
@@ -257,6 +257,9 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
device->ClearComposition = WIN_ClearComposition;
device->IsTextInputShown = WIN_IsTextInputShown;
+ device->SetClipboardData = WIN_SetClipboardData;
+ device->GetClipboardData = WIN_GetClipboardData;
+ device->HasClipboardData = WIN_HasClipboardData;
device->SetClipboardText = WIN_SetClipboardText;
device->GetClipboardText = WIN_GetClipboardText;
device->HasClipboardText = WIN_HasClipboardText;