Added support for clipboard data on Windows

The only supported image format is image/bmp
This commit is contained in:
Sam Lantinga 2023-07-04 11:09:43 -07:00
parent 35876da3c4
commit 443868143c
3 changed files with 204 additions and 30 deletions

View File

@ -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)

View File

@ -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);

View File

@ -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;