From 443868143ce3d925f575c66e2218edcd8bc884d7 Mon Sep 17 00:00:00 2001 From: Sam Lantinga 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 7af109c44..2ecb081f8 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) { - SDL_Window *window; + /* Retry to open the clipboard in case another application has it open */ + const int MAX_ATTEMPTS = 3; + int attempt; + HWND hwnd = NULL; - window = _this->windows; - if (window) { - return window->driverdata->hwnd; + if (_this->windows) { + hwnd = _this->windows->driverdata->hwnd; } - return NULL; + 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) +{ + 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; +} + +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 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; + if (WIN_OpenClipboard(_this)) { + HANDLE hMem; + LPTSTR tstr; - for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { - if (OpenClipboard(GetWindowHandle(_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 fd1cc5229..beb068dae 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 b1617385b..d8669bdc1 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;