#if !defined(_WIN32) # error "This module is only made for Windows OS" #endif #ifndef WIN32_CLIPBOARD_ #define WIN32_CLIPBOARD_ unsigned char* Win32GetClipboardImageData(int* width, int* height, unsigned long long int *dataSize); #endif // WIN32_CLIPBOARD_ #ifdef WIN32_CLIPBOARD_IMPLEMENTATION #include #include #include #include // NOTE: These search for architecture is taken from "Windows.h", and it's necessary if we really don't wanna import windows.h // and still make it compile on msvc, because import indirectly importing "winnt.h" (e.g. ) can cause problems is these are not defined. #if !defined(_X86_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_IX86) #define _X86_ #if !defined(_CHPE_X86_ARM64_) && defined(_M_HYBRID) #define _CHPE_X86_ARM64_ #endif #endif #if !defined(_AMD64_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && (defined(_M_AMD64) || defined(_M_ARM64EC)) #define _AMD64_ #endif #if !defined(_ARM_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_ARM) #define _ARM_ #endif #if !defined(_ARM64_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64EC_) && defined(_M_ARM64) #define _ARM64_ #endif #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_ARM64EC) #define _ARM64EC_ #endif #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_M68K) #define _68K_ #endif #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_MPPC) #define _MPPC_ #endif #if !defined(_IA64_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_M_IX86) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_IA64) #define _IA64_ #endif #define WIN32_LEAN_AND_MEAN // #include // #include // #include #include // #include #ifndef WINAPI #if defined(_ARM_) #define WINAPI #else #define WINAPI __stdcall #endif #endif #ifndef WINAPI #if defined(_ARM_) #define WINAPI #else #define WINAPI __stdcall #endif #endif #ifndef WINBASEAPI #ifndef _KERNEL32_ #define WINBASEAPI DECLSPEC_IMPORT #else #define WINBASEAPI #endif #endif #ifndef WINUSERAPI #ifndef _USER32_ #define WINUSERAPI __declspec (dllimport) #else #define WINUSERAPI #endif #endif typedef int WINBOOL; // typedef HANDLE HGLOBAL; #ifndef HWND #define HWND void* #endif #if !defined(_WINUSER_) || !defined(WINUSER_ALREADY_INCLUDED) WINUSERAPI WINBOOL WINAPI OpenClipboard(HWND hWndNewOwner); WINUSERAPI WINBOOL WINAPI CloseClipboard(VOID); WINUSERAPI DWORD WINAPI GetClipboardSequenceNumber(VOID); WINUSERAPI HWND WINAPI GetClipboardOwner(VOID); WINUSERAPI HWND WINAPI SetClipboardViewer(HWND hWndNewViewer); WINUSERAPI HWND WINAPI GetClipboardViewer(VOID); WINUSERAPI WINBOOL WINAPI ChangeClipboardChain(HWND hWndRemove, HWND hWndNewNext); WINUSERAPI HANDLE WINAPI SetClipboardData(UINT uFormat, HANDLE hMem); WINUSERAPI HANDLE WINAPI GetClipboardData(UINT uFormat); WINUSERAPI UINT WINAPI RegisterClipboardFormatA(LPCSTR lpszFormat); WINUSERAPI UINT WINAPI RegisterClipboardFormatW(LPCWSTR lpszFormat); WINUSERAPI int WINAPI CountClipboardFormats(VOID); WINUSERAPI UINT WINAPI EnumClipboardFormats(UINT format); WINUSERAPI int WINAPI GetClipboardFormatNameA(UINT format, LPSTR lpszFormatName, int cchMaxCount); WINUSERAPI int WINAPI GetClipboardFormatNameW(UINT format, LPWSTR lpszFormatName, int cchMaxCount); WINUSERAPI WINBOOL WINAPI EmptyClipboard(VOID); WINUSERAPI WINBOOL WINAPI IsClipboardFormatAvailable(UINT format); WINUSERAPI int WINAPI GetPriorityClipboardFormat(UINT *paFormatPriorityList, int cFormats); WINUSERAPI HWND WINAPI GetOpenClipboardWindow(VOID); #endif #ifndef HGLOBAL #define HGLOBAL void* #endif #if !defined(_WINBASE_) || !defined(WINBASE_ALREADY_INCLUDED) WINBASEAPI SIZE_T WINAPI GlobalSize (HGLOBAL hMem); WINBASEAPI LPVOID WINAPI GlobalLock (HGLOBAL hMem); WINBASEAPI WINBOOL WINAPI GlobalUnlock (HGLOBAL hMem); #endif #if !defined(_WINGDI_) || !defined(WINGDI_ALREADY_INCLUDED) #ifndef BITMAPINFOHEADER_ALREADY_DEFINED #define BITMAPINFOHEADER_ALREADY_DEFINED // Does this header need to be packed ? by the windowps header it doesnt seem to be #pragma pack(push, 1) typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER,*LPBITMAPINFOHEADER,*PBITMAPINFOHEADER; #pragma pack(pop) #endif #ifndef BITMAPFILEHEADER_ALREADY_DEFINED #define BITMAPFILEHEADER_ALREADY_DEFINED #pragma pack(push, 1) typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER,*LPBITMAPFILEHEADER,*PBITMAPFILEHEADER; #pragma pack(pop) #endif #ifndef RGBQUAD_ALREADY_DEFINED #define RGBQUAD_ALREADY_DEFINED typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD, *LPRGBQUAD; #endif // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/4e588f70-bd92-4a6f-b77f-35d0feaf7a57 #define BI_RGB 0x0000 #define BI_RLE8 0x0001 #define BI_RLE4 0x0002 #define BI_BITFIELDS 0x0003 #define BI_JPEG 0x0004 #define BI_PNG 0x0005 #define BI_CMYK 0x000B #define BI_CMYKRLE8 0x000C #define BI_CMYKRLE4 0x000D #endif // https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats #define CF_DIB 8 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setsystemcursor // #define OCR_NORMAL 32512 // Normal select // #define OCR_IBEAM 32513 // Text select // #define OCR_WAIT 32514 // Busy // #define OCR_CROSS 32515 // Precision select // #define OCR_UP 32516 // Alternate select // #define OCR_SIZENWSE 32642 // Diagonal resize 1 // #define OCR_SIZENESW 32643 // Diagonal resize 2 // #define OCR_SIZEWE 32644 // Horizontal resize // #define OCR_SIZENS 32645 // Vertical resize // #define OCR_SIZEALL 32646 // Move // #define OCR_NO 32648 // Unavailable // #define OCR_HAND 32649 // Link select // #define OCR_APPSTARTING 32650 // //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- static BOOL OpenClipboardRetrying(HWND handle); // Open clipboard with a number of retries static int GetPixelDataOffset(BITMAPINFOHEADER bih); unsigned char* Win32GetClipboardImageData(int* width, int* height, unsigned long long int *dataSize) { HWND win = NULL; // Get from somewhere but is doesnt seem to matter const char* msgString = ""; int severity = LOG_INFO; BYTE* bmpData = NULL; if (!OpenClipboardRetrying(win)) { severity = LOG_ERROR; msgString = "Couldn't open clipboard"; goto end; } HGLOBAL clipHandle = (HGLOBAL)GetClipboardData(CF_DIB); if (!clipHandle) { severity = LOG_ERROR; msgString = "Clipboard data is not an Image"; goto close; } BITMAPINFOHEADER *bmpInfoHeader = (BITMAPINFOHEADER *)GlobalLock(clipHandle); if (!bmpInfoHeader) { // Mapping from HGLOBAL to our local *address space* failed severity = LOG_ERROR; msgString = "Clipboard data failed to be locked"; goto unlock; } *width = bmpInfoHeader->biWidth; *height = bmpInfoHeader->biHeight; SIZE_T clipDataSize = GlobalSize(clipHandle); if (clipDataSize < sizeof(BITMAPINFOHEADER)) { // Format CF_DIB needs space for BITMAPINFOHEADER struct. msgString = "Clipboard has Malformed data"; severity = LOG_ERROR; goto unlock; } // Denotes where the pixel data starts from the bmpInfoHeader pointer int pixelOffset = GetPixelDataOffset(*bmpInfoHeader); //--------------------------------------------------------------------------------// // // The rest of the section is about create the bytes for a correct BMP file // Then we copy the data and to a pointer // //--------------------------------------------------------------------------------// BITMAPFILEHEADER bmpFileHeader = {0}; SIZE_T bmpFileSize = sizeof(bmpFileHeader) + clipDataSize; *dataSize = bmpFileSize; bmpFileHeader.bfType = 0x4D42; //https://stackoverflow.com/questions/601430/multibyte-character-constants-and-bitmap-file-header-type-constants#601536 bmpFileHeader.bfSize = (DWORD)bmpFileSize; // Up to 4GB works fine bmpFileHeader.bfOffBits = sizeof(bmpFileHeader) + pixelOffset; // // Each process has a default heap provided by the system // Memory objects allocated by GlobalAlloc and LocalAlloc are in private, // committed pages with read/write access that cannot be accessed by other processes. // // This may be wrong since we might be allocating in a DLL and freeing from another module, the main application // that may cause heap corruption. We could create a FreeImage function // bmpData = malloc(sizeof(bmpFileHeader) + clipDataSize); // First we add the header for a bmp file memcpy(bmpData, &bmpFileHeader, sizeof(bmpFileHeader)); // Then we add the header for the bmp itself + the pixel data memcpy(bmpData + sizeof(bmpFileHeader), bmpInfoHeader, clipDataSize); msgString = "Clipboad image acquired successfully"; unlock: GlobalUnlock(clipHandle); close: CloseClipboard(); end: TRACELOG(severity, msgString); return bmpData; } static BOOL OpenClipboardRetrying(HWND hWnd) { static const int maxTries = 20; static const int sleepTimeMS = 60; for (int _ = 0; _ < maxTries; ++_) { // Might be being hold by another process // Or yourself forgot to CloseClipboard if (OpenClipboard(hWnd)) { return true; } Sleep(sleepTimeMS); } return false; } // Based off of researching microsoft docs and reponses from this question https://stackoverflow.com/questions/30552255/how-to-read-a-bitmap-from-the-windows-clipboard#30552856 // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader // Get the byte offset where does the pixels data start (from a packed DIB) static int GetPixelDataOffset(BITMAPINFOHEADER bih) { int offset = 0; const unsigned int rgbaSize = sizeof(RGBQUAD); // biSize Specifies the number of bytes required by the structure // We expect to always be 40 because it should be packed if (40 == bih.biSize && 40 == sizeof(BITMAPINFOHEADER)) { // // biBitCount Specifies the number of bits per pixel. // Might exist some bit masks *after* the header and *before* the pixel offset // we're looking, but only if we have more than // 8 bits per pixel, so we need to ajust for that // if (bih.biBitCount > 8) { // if bih.biCompression is RBG we should NOT offset more if (bih.biCompression == BI_BITFIELDS) { offset += 3 * rgbaSize; } else if (bih.biCompression == 6 /* BI_ALPHABITFIELDS */) { // Not widely supported, but valid. offset += 4 * rgbaSize; } } } // // biClrUsed Specifies the number of color indices in the color table that are actually used by the bitmap. // If this value is zero, the bitmap uses the maximum number of colors // corresponding to the value of the biBitCount member for the compression mode specified by biCompression. // If biClrUsed is nonzero and the biBitCount member is less than 16 // the biClrUsed member specifies the actual number of colors // if (bih.biClrUsed > 0) { offset += bih.biClrUsed * rgbaSize; } else { if (bih.biBitCount < 16) { offset = offset + (rgbaSize << bih.biBitCount); } } return bih.biSize + offset; } #endif // WIN32_CLIPBOARD_IMPLEMENTATION // EOF