win32_clipboard.h (13022B)
1 #if !defined(_WIN32) 2 # error "This module is only made for Windows OS" 3 #endif 4 5 #ifndef WIN32_CLIPBOARD_ 6 #define WIN32_CLIPBOARD_ 7 unsigned char* Win32GetClipboardImageData(int* width, int* height, unsigned long long int *dataSize); 8 #endif // WIN32_CLIPBOARD_ 9 10 #ifdef WIN32_CLIPBOARD_IMPLEMENTATION 11 #include <stdio.h> 12 #include <stdbool.h> 13 #include <stdlib.h> 14 #include <assert.h> 15 16 // NOTE: These search for architecture is taken from "Windows.h", and it's necessary if we really don't wanna import windows.h 17 // and still make it compile on msvc, because import indirectly importing "winnt.h" (e.g. <minwindef.h>) can cause problems is these are not defined. 18 #if !defined(_X86_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_IX86) 19 #define _X86_ 20 #if !defined(_CHPE_X86_ARM64_) && defined(_M_HYBRID) 21 #define _CHPE_X86_ARM64_ 22 #endif 23 #endif 24 25 #if !defined(_AMD64_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && (defined(_M_AMD64) || defined(_M_ARM64EC)) 26 #define _AMD64_ 27 #endif 28 29 #if !defined(_ARM_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_ARM) 30 #define _ARM_ 31 #endif 32 33 #if !defined(_ARM64_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64EC_) && defined(_M_ARM64) 34 #define _ARM64_ 35 #endif 36 37 #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_ARM64EC) 38 #define _ARM64EC_ 39 #endif 40 41 #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_M68K) 42 #define _68K_ 43 #endif 44 45 #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_MPPC) 46 #define _MPPC_ 47 #endif 48 49 #if !defined(_IA64_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_M_IX86) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_IA64) 50 #define _IA64_ 51 #endif 52 53 54 #define WIN32_LEAN_AND_MEAN 55 // #include <sdkddkver.h> 56 // #include <windows.h> 57 // #include <winuser.h> 58 #include <minwindef.h> 59 // #include <minwinbase.h> 60 61 #ifndef WINAPI 62 #if defined(_ARM_) 63 #define WINAPI 64 #else 65 #define WINAPI __stdcall 66 #endif 67 #endif 68 69 #ifndef WINAPI 70 #if defined(_ARM_) 71 #define WINAPI 72 #else 73 #define WINAPI __stdcall 74 #endif 75 #endif 76 77 #ifndef WINBASEAPI 78 #ifndef _KERNEL32_ 79 #define WINBASEAPI DECLSPEC_IMPORT 80 #else 81 #define WINBASEAPI 82 #endif 83 #endif 84 85 #ifndef WINUSERAPI 86 #ifndef _USER32_ 87 #define WINUSERAPI __declspec (dllimport) 88 #else 89 #define WINUSERAPI 90 #endif 91 #endif 92 93 typedef int WINBOOL; 94 95 96 97 // typedef HANDLE HGLOBAL; 98 99 #ifndef HWND 100 #define HWND void* 101 #endif 102 103 104 #if !defined(_WINUSER_) || !defined(WINUSER_ALREADY_INCLUDED) 105 WINUSERAPI WINBOOL WINAPI OpenClipboard(HWND hWndNewOwner); 106 WINUSERAPI WINBOOL WINAPI CloseClipboard(VOID); 107 WINUSERAPI DWORD WINAPI GetClipboardSequenceNumber(VOID); 108 WINUSERAPI HWND WINAPI GetClipboardOwner(VOID); 109 WINUSERAPI HWND WINAPI SetClipboardViewer(HWND hWndNewViewer); 110 WINUSERAPI HWND WINAPI GetClipboardViewer(VOID); 111 WINUSERAPI WINBOOL WINAPI ChangeClipboardChain(HWND hWndRemove, HWND hWndNewNext); 112 WINUSERAPI HANDLE WINAPI SetClipboardData(UINT uFormat, HANDLE hMem); 113 WINUSERAPI HANDLE WINAPI GetClipboardData(UINT uFormat); 114 WINUSERAPI UINT WINAPI RegisterClipboardFormatA(LPCSTR lpszFormat); 115 WINUSERAPI UINT WINAPI RegisterClipboardFormatW(LPCWSTR lpszFormat); 116 WINUSERAPI int WINAPI CountClipboardFormats(VOID); 117 WINUSERAPI UINT WINAPI EnumClipboardFormats(UINT format); 118 WINUSERAPI int WINAPI GetClipboardFormatNameA(UINT format, LPSTR lpszFormatName, int cchMaxCount); 119 WINUSERAPI int WINAPI GetClipboardFormatNameW(UINT format, LPWSTR lpszFormatName, int cchMaxCount); 120 WINUSERAPI WINBOOL WINAPI EmptyClipboard(VOID); 121 WINUSERAPI WINBOOL WINAPI IsClipboardFormatAvailable(UINT format); 122 WINUSERAPI int WINAPI GetPriorityClipboardFormat(UINT *paFormatPriorityList, int cFormats); 123 WINUSERAPI HWND WINAPI GetOpenClipboardWindow(VOID); 124 #endif 125 126 #ifndef HGLOBAL 127 #define HGLOBAL void* 128 #endif 129 130 #if !defined(_WINBASE_) || !defined(WINBASE_ALREADY_INCLUDED) 131 WINBASEAPI SIZE_T WINAPI GlobalSize (HGLOBAL hMem); 132 WINBASEAPI LPVOID WINAPI GlobalLock (HGLOBAL hMem); 133 WINBASEAPI WINBOOL WINAPI GlobalUnlock (HGLOBAL hMem); 134 #endif 135 136 137 #if !defined(_WINGDI_) || !defined(WINGDI_ALREADY_INCLUDED) 138 #ifndef BITMAPINFOHEADER_ALREADY_DEFINED 139 #define BITMAPINFOHEADER_ALREADY_DEFINED 140 // Does this header need to be packed ? by the windowps header it doesnt seem to be 141 #pragma pack(push, 1) 142 typedef struct tagBITMAPINFOHEADER { 143 DWORD biSize; 144 LONG biWidth; 145 LONG biHeight; 146 WORD biPlanes; 147 WORD biBitCount; 148 DWORD biCompression; 149 DWORD biSizeImage; 150 LONG biXPelsPerMeter; 151 LONG biYPelsPerMeter; 152 DWORD biClrUsed; 153 DWORD biClrImportant; 154 } BITMAPINFOHEADER,*LPBITMAPINFOHEADER,*PBITMAPINFOHEADER; 155 #pragma pack(pop) 156 #endif 157 158 #ifndef BITMAPFILEHEADER_ALREADY_DEFINED 159 #define BITMAPFILEHEADER_ALREADY_DEFINED 160 #pragma pack(push, 1) 161 typedef struct tagBITMAPFILEHEADER { 162 WORD bfType; 163 DWORD bfSize; 164 WORD bfReserved1; 165 WORD bfReserved2; 166 DWORD bfOffBits; 167 } BITMAPFILEHEADER,*LPBITMAPFILEHEADER,*PBITMAPFILEHEADER; 168 #pragma pack(pop) 169 #endif 170 171 #ifndef RGBQUAD_ALREADY_DEFINED 172 #define RGBQUAD_ALREADY_DEFINED 173 typedef struct tagRGBQUAD { 174 BYTE rgbBlue; 175 BYTE rgbGreen; 176 BYTE rgbRed; 177 BYTE rgbReserved; 178 } RGBQUAD, *LPRGBQUAD; 179 #endif 180 181 182 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/4e588f70-bd92-4a6f-b77f-35d0feaf7a57 183 #define BI_RGB 0x0000 184 #define BI_RLE8 0x0001 185 #define BI_RLE4 0x0002 186 #define BI_BITFIELDS 0x0003 187 #define BI_JPEG 0x0004 188 #define BI_PNG 0x0005 189 #define BI_CMYK 0x000B 190 #define BI_CMYKRLE8 0x000C 191 #define BI_CMYKRLE4 0x000D 192 193 #endif 194 195 // https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats 196 #define CF_DIB 8 197 198 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setsystemcursor 199 // #define OCR_NORMAL 32512 // Normal select 200 // #define OCR_IBEAM 32513 // Text select 201 // #define OCR_WAIT 32514 // Busy 202 // #define OCR_CROSS 32515 // Precision select 203 // #define OCR_UP 32516 // Alternate select 204 // #define OCR_SIZENWSE 32642 // Diagonal resize 1 205 // #define OCR_SIZENESW 32643 // Diagonal resize 2 206 // #define OCR_SIZEWE 32644 // Horizontal resize 207 // #define OCR_SIZENS 32645 // Vertical resize 208 // #define OCR_SIZEALL 32646 // Move 209 // #define OCR_NO 32648 // Unavailable 210 // #define OCR_HAND 32649 // Link select 211 // #define OCR_APPSTARTING 32650 // 212 213 214 //---------------------------------------------------------------------------------- 215 // Module Internal Functions Declaration 216 //---------------------------------------------------------------------------------- 217 218 219 static BOOL OpenClipboardRetrying(HWND handle); // Open clipboard with a number of retries 220 static int GetPixelDataOffset(BITMAPINFOHEADER bih); 221 222 unsigned char* Win32GetClipboardImageData(int* width, int* height, unsigned long long int *dataSize) 223 { 224 HWND win = NULL; // Get from somewhere but is doesnt seem to matter 225 const char* msgString = ""; 226 int severity = LOG_INFO; 227 BYTE* bmpData = NULL; 228 if (!OpenClipboardRetrying(win)) { 229 severity = LOG_ERROR; 230 msgString = "Couldn't open clipboard"; 231 goto end; 232 } 233 234 HGLOBAL clipHandle = (HGLOBAL)GetClipboardData(CF_DIB); 235 if (!clipHandle) { 236 severity = LOG_ERROR; 237 msgString = "Clipboard data is not an Image"; 238 goto close; 239 } 240 241 BITMAPINFOHEADER *bmpInfoHeader = (BITMAPINFOHEADER *)GlobalLock(clipHandle); 242 if (!bmpInfoHeader) { 243 // Mapping from HGLOBAL to our local *address space* failed 244 severity = LOG_ERROR; 245 msgString = "Clipboard data failed to be locked"; 246 goto unlock; 247 } 248 249 *width = bmpInfoHeader->biWidth; 250 *height = bmpInfoHeader->biHeight; 251 252 SIZE_T clipDataSize = GlobalSize(clipHandle); 253 if (clipDataSize < sizeof(BITMAPINFOHEADER)) { 254 // Format CF_DIB needs space for BITMAPINFOHEADER struct. 255 msgString = "Clipboard has Malformed data"; 256 severity = LOG_ERROR; 257 goto unlock; 258 } 259 260 // Denotes where the pixel data starts from the bmpInfoHeader pointer 261 int pixelOffset = GetPixelDataOffset(*bmpInfoHeader); 262 263 //--------------------------------------------------------------------------------// 264 // 265 // The rest of the section is about create the bytes for a correct BMP file 266 // Then we copy the data and to a pointer 267 // 268 //--------------------------------------------------------------------------------// 269 270 BITMAPFILEHEADER bmpFileHeader = {0}; 271 SIZE_T bmpFileSize = sizeof(bmpFileHeader) + clipDataSize; 272 *dataSize = bmpFileSize; 273 274 bmpFileHeader.bfType = 0x4D42; //https://stackoverflow.com/questions/601430/multibyte-character-constants-and-bitmap-file-header-type-constants#601536 275 276 bmpFileHeader.bfSize = (DWORD)bmpFileSize; // Up to 4GB works fine 277 bmpFileHeader.bfOffBits = sizeof(bmpFileHeader) + pixelOffset; 278 279 // 280 // Each process has a default heap provided by the system 281 // Memory objects allocated by GlobalAlloc and LocalAlloc are in private, 282 // committed pages with read/write access that cannot be accessed by other processes. 283 // 284 // This may be wrong since we might be allocating in a DLL and freeing from another module, the main application 285 // that may cause heap corruption. We could create a FreeImage function 286 // 287 bmpData = malloc(sizeof(bmpFileHeader) + clipDataSize); 288 // First we add the header for a bmp file 289 memcpy(bmpData, &bmpFileHeader, sizeof(bmpFileHeader)); 290 // Then we add the header for the bmp itself + the pixel data 291 memcpy(bmpData + sizeof(bmpFileHeader), bmpInfoHeader, clipDataSize); 292 msgString = "Clipboad image acquired successfully"; 293 294 295 unlock: 296 GlobalUnlock(clipHandle); 297 close: 298 CloseClipboard(); 299 end: 300 301 TRACELOG(severity, msgString); 302 return bmpData; 303 } 304 305 static BOOL OpenClipboardRetrying(HWND hWnd) 306 { 307 static const int maxTries = 20; 308 static const int sleepTimeMS = 60; 309 for (int _ = 0; _ < maxTries; ++_) 310 { 311 // Might be being hold by another process 312 // Or yourself forgot to CloseClipboard 313 if (OpenClipboard(hWnd)) { 314 return true; 315 } 316 Sleep(sleepTimeMS); 317 } 318 return false; 319 } 320 321 // 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 322 // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader 323 // Get the byte offset where does the pixels data start (from a packed DIB) 324 static int GetPixelDataOffset(BITMAPINFOHEADER bih) 325 { 326 int offset = 0; 327 const unsigned int rgbaSize = sizeof(RGBQUAD); 328 329 // biSize Specifies the number of bytes required by the structure 330 // We expect to always be 40 because it should be packed 331 if (40 == bih.biSize && 40 == sizeof(BITMAPINFOHEADER)) 332 { 333 // 334 // biBitCount Specifies the number of bits per pixel. 335 // Might exist some bit masks *after* the header and *before* the pixel offset 336 // we're looking, but only if we have more than 337 // 8 bits per pixel, so we need to ajust for that 338 // 339 if (bih.biBitCount > 8) 340 { 341 // if bih.biCompression is RBG we should NOT offset more 342 343 if (bih.biCompression == BI_BITFIELDS) 344 { 345 offset += 3 * rgbaSize; 346 } else if (bih.biCompression == 6 /* BI_ALPHABITFIELDS */) 347 { 348 // Not widely supported, but valid. 349 offset += 4 * rgbaSize; 350 } 351 } 352 } 353 354 // 355 // biClrUsed Specifies the number of color indices in the color table that are actually used by the bitmap. 356 // If this value is zero, the bitmap uses the maximum number of colors 357 // corresponding to the value of the biBitCount member for the compression mode specified by biCompression. 358 // If biClrUsed is nonzero and the biBitCount member is less than 16 359 // the biClrUsed member specifies the actual number of colors 360 // 361 if (bih.biClrUsed > 0) { 362 offset += bih.biClrUsed * rgbaSize; 363 } else { 364 if (bih.biBitCount < 16) 365 { 366 offset = offset + (rgbaSize << bih.biBitCount); 367 } 368 } 369 370 return bih.biSize + offset; 371 } 372 #endif // WIN32_CLIPBOARD_IMPLEMENTATION 373 // EOF 374