From 00396e34369a1696180849216c3e5aca264c70e4 Mon Sep 17 00:00:00 2001 From: "Everton Jr." <69195288+evertonse@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:40:41 -0300 Subject: [PATCH] [rcore] Clipboard Image Support (#4459) * [rcore] add 'GetClipboardImage' for windows * [rcore] GetClipboardImage removed some unneeded defines * [rcore] PLATFORM_SDL: create a compatility layer for SDL3 * external: add win32_clipboard.h header only lib * [rcore] using win32_clipboard on platforms rlfw and rgfw * [rcore] fix warnings in SDL3 compatibility layer * Makefile: Allow specifying SDL_LIBRARIES to link, this helps with SDL3 * Makefile: examples makefile now compile others/rlgl_standalone only when TARGET_PLATFORM is PLATFORM_DESKTOP_GFLW * Makefile: examples makefile now compile others/rlgl_standalone only when TARGET_PLATFORM is PLATFORM_DESKTOP_GFLW * [rcore]: PLATFORM_SDL: improve clipboard data retrieval * external: remove unused function from win32_clipboard.h * Makefile: allow for extra flags necessary when compiling for SDL3 * [rcore]: fix string typo * [rcore]: Properly handle NULL dpi passing. As is allowed in SDL2 * external: fix arch finding on win32_clipboard.h to allow compilation on msvc cmake CI * [rcore]: PLATFORM_SDL: Treat monitor as an ID in SDL3 as opposed to an index as in SDL2 * [rcore]: typo --- examples/Makefile | 18 +- src/Makefile | 6 +- src/config.h | 28 +++ src/external/win32_clipboard.h | 374 +++++++++++++++++++++++++++++ src/platforms/rcore_desktop_glfw.c | 32 +++ src/platforms/rcore_desktop_rgfw.c | 37 +++ src/platforms/rcore_desktop_sdl.c | 346 +++++++++++++++++++++++++- src/raylib.h | 1 + src/rcore.c | 6 + 9 files changed, 837 insertions(+), 11 deletions(-) create mode 100644 src/external/win32_clipboard.h diff --git a/examples/Makefile b/examples/Makefile index 304599cf..a47911b5 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -87,6 +87,8 @@ USE_EXTERNAL_GLFW ?= FALSE # WARNING: Library is not included in raylib, it MUST be configured by users SDL_INCLUDE_PATH ?= $(RAYLIB_SRC_PATH)/external/SDL2/include SDL_LIBRARY_PATH ?= $(RAYLIB_SRC_PATH)/external/SDL2/lib +SDL_LIBRARIES ?= -lSDL2 -lSDL2main + # Use Wayland display server protocol on Linux desktop (by default it uses X11 windowing system) # NOTE: This variable is only used for PLATFORM_OS: LINUX @@ -263,9 +265,9 @@ endif # Define include paths for required headers: INCLUDE_PATHS # NOTE: Some external/extras libraries could be required (stb, easings...) #------------------------------------------------------------------------------------------------ -INCLUDE_PATHS = -I. -I$(RAYLIB_PATH)/src -I$(RAYLIB_PATH)/src/external - +INCLUDE_PATHS = -I. -I$(RAYLIB_PATH)/src -I$(RAYLIB_PATH)/src/external $(EXTRA_INCLUDE_PATHS) # Define additional directories containing required header files + ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW) ifeq ($(PLATFORM_OS),BSD) INCLUDE_PATHS += -I$(RAYLIB_INCLUDE_PATH) -I/usr/pkg/include -I/usr/X11R7/include @@ -415,12 +417,12 @@ endif ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL) ifeq ($(PLATFORM_OS),WINDOWS) # Libraries for Windows desktop compilation - LDLIBS = -lraylib -lSDL2 -lSDL2main -lopengl32 -lgdi32 + LDLIBS = -lraylib $(SDL_LIBRARIES) -lopengl32 -lgdi32 endif ifeq ($(PLATFORM_OS),LINUX) # Libraries for Debian GNU/Linux desktop compiling # NOTE: Required packages: libegl1-mesa-dev - LDLIBS = -lraylib -lSDL2 -lSDL2main -lGL -lm -lpthread -ldl -lrt + LDLIBS = -lraylib $(SDL_LIBRARIES) -lGL -lm -lpthread -ldl -lrt # On X11 requires also below libraries LDLIBS += -lX11 @@ -646,8 +648,12 @@ OTHERS = \ others/embedded_files_loading \ others/raylib_opengl_interop \ others/raymath_vector_angle \ - others/rlgl_compute_shader \ - others/rlgl_standalone + others/rlgl_compute_shader + +ifeq ($(TARGET_PLATFORM), PLATFORM_DESKTOP_GFLW) + OTHERS += others/rlgl_standalone +endif + CURRENT_MAKEFILE = $(lastword $(MAKEFILE_LIST)) diff --git a/src/Makefile b/src/Makefile index 1f0b730c..265f95ca 100644 --- a/src/Makefile +++ b/src/Makefile @@ -118,6 +118,8 @@ GLFW_LINUX_ENABLE_X11 ?= TRUE # WARNING: Library is not included in raylib, it MUST be configured by users SDL_INCLUDE_PATH ?= $(RAYLIB_SRC_PATH)/external/SDL2/include SDL_LIBRARY_PATH ?= $(RAYLIB_SRC_PATH)/external/SDL2/lib +SDL_LIBRARIES ?= -lSDL2 -lSDL2main + # Determine if the file has root access (only required to install raylib) # "whoami" prints the name of the user that calls him (so, if it is the root user, "whoami" prints "root") @@ -460,7 +462,7 @@ CFLAGS += $(CUSTOM_CFLAGS) # Define include paths for required headers: INCLUDE_PATHS # NOTE: Several external required libraries (stb and others) #------------------------------------------------------------------------------------------------ -INCLUDE_PATHS = -I. +INCLUDE_PATHS = -I. $(EXTRA_INCLUDE_PATHS) # Define additional directories containing required header files ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW) @@ -586,7 +588,7 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL) LDLIBS += -lX11 endif endif - LDLIBS += -lSDL2 -lSDL2main + LDLIBS += $(SDL_LIBRARIES) endif ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_RGFW) ifeq ($(PLATFORM_OS),WINDOWS) diff --git a/src/config.h b/src/config.h index f37a9de3..e3749c56 100644 --- a/src/config.h +++ b/src/config.h @@ -71,6 +71,7 @@ // Enabling this flag allows manual control of the frame processes, use at your own risk //#define SUPPORT_CUSTOM_FRAME_CONTROL 1 + // rcore: Configuration values //------------------------------------------------------------------------------------ #define MAX_FILEPATH_CAPACITY 8192 // Maximum file paths capacity @@ -272,4 +273,31 @@ //------------------------------------------------------------------------------------ #define MAX_TRACELOG_MSG_LENGTH 256 // Max length of one trace-log message + +// Enable partial support for clipboard image, only working on SDL3 or +// being on both Windows OS + GLFW or Windows OS + RGFW +#define SUPPORT_CLIPBOARD_IMAGE 1 + +#if defined(SUPPORT_CLIPBOARD_IMAGE) + #ifndef STBI_REQUIRED + #define STBI_REQUIRED + #endif + + #ifndef SUPPORT_FILEFORMAT_BMP // For clipboard image on Windows + #define SUPPORT_FILEFORMAT_BMP 1 + #endif + + #ifndef SUPPORT_FILEFORMAT_PNG // Wayland uses png for prints, at least it was on 22 LTS ubuntu + #define SUPPORT_FILEFORMAT_PNG 1 + #endif + + #ifndef SUPPORT_FILEFORMAT_JPG + #define SUPPORT_FILEFORMAT_JPG 1 + #endif + + #ifndef SUPPORT_MODULE_RTEXTURES + #define SUPPORT_MODULE_RTEXTURES 1 + #endif +#endif + #endif // CONFIG_H diff --git a/src/external/win32_clipboard.h b/src/external/win32_clipboard.h new file mode 100644 index 00000000..83285643 --- /dev/null +++ b/src/external/win32_clipboard.h @@ -0,0 +1,374 @@ +#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 + diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 45811e76..e765debc 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -58,6 +58,7 @@ #if defined(_WIN32) typedef void *PVOID; typedef PVOID HANDLE; + #include "../external/win32_clipboard.h" typedef HANDLE HWND; #define GLFW_EXPOSE_NATIVE_WIN32 #define GLFW_NATIVE_INCLUDE_NONE // To avoid some symbols re-definition in windows.h @@ -966,6 +967,33 @@ const char *GetClipboardText(void) return glfwGetClipboardString(platform.handle); } +#if defined(SUPPORT_CLIPBOARD_IMAGE) +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = {0}; + unsigned long long int dataSize = 0; + void* fileData = NULL; + +#ifdef _WIN32 + int width, height; + fileData = (void*)Win32GetClipboardImageData(&width, &height, &dataSize); +#else + TRACELOG(LOG_WARNING, "Clipboard image: PLATFORM_DESKTOP_GLFW doesn't implement `GetClipboardImage` for this OS"); +#endif + + if (fileData == NULL) + { + TRACELOG(LOG_WARNING, "Clipboard image: Couldn't get clipboard data."); + } + else + { + image = LoadImageFromMemory(".bmp", fileData, dataSize); + } + return image; +} +#endif // SUPPORT_CLIPBOARD_IMAGE + // Show mouse cursor void ShowCursor(void) { @@ -1898,4 +1926,8 @@ static void JoystickCallback(int jid, int event) } } +#ifdef _WIN32 +# define WIN32_CLIPBOARD_IMPLEMENTATION +# include "../external/win32_clipboard.h" +#endif // EOF diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 68885ce3..f3f26e3e 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -664,6 +664,43 @@ const char *GetClipboardText(void) return RGFW_readClipboard(NULL); } + +#if defined(SUPPORT_CLIPBOARD_IMAGE) + +#ifdef _WIN32 +# define WIN32_CLIPBOARD_IMPLEMENTATION +# define WINUSER_ALREADY_INCLUDED +# define WINBASE_ALREADY_INCLUDED +# define WINGDI_ALREADY_INCLUDED +# include "../external/win32_clipboard.h" +#endif + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = {0}; + unsigned long long int dataSize = 0; + void* fileData = NULL; + +#ifdef _WIN32 + int width, height; + fileData = (void*)Win32GetClipboardImageData(&width, &height, &dataSize); +#else + TRACELOG(LOG_WARNING, "Clipboard image: PLATFORM_DESKTOP_RGFW doesn't implement `GetClipboardImage` for this OS"); +#endif + + if (fileData == NULL) + { + TRACELOG(LOG_WARNING, "Clipboard image: Couldn't get clipboard data."); + } + else + { + image = LoadImageFromMemory(".bmp", fileData, dataSize); + } + return image; +} +#endif // SUPPORT_CLIPBOARD_IMAGE + // Show mouse cursor void ShowCursor(void) { diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 7cbe0354..a201f2cd 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -23,7 +23,7 @@ * Custom flag for rcore on target platform -not used- * * DEPENDENCIES: -* - SDL 2 (main library): Windowing and inputs management +* - SDL 2 or SLD 3 (main library): Windowing and inputs management * - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) * * @@ -48,6 +48,10 @@ * **********************************************************************************************/ + +#ifndef SDL_ENABLE_OLD_NAMES + #define SDL_ENABLE_OLD_NAMES // Just in case we're on SDL3, we need some in-between compatibily +#endif #include "SDL.h" // SDL base library (window/rendered, input, timing... functionality) #if defined(GRAPHICS_API_OPENGL_ES2) @@ -64,6 +68,13 @@ #define MAX_CLIPBOARD_BUFFER_LENGTH 1024 // Size of the clipboard buffer used on GetClipboardText() #endif +#if ((defined(SDL_MAJOR_VERSION) && SDL_MAJOR_VERSION == 3) && (defined(SDL_MINOR_VERSION) && SDL_MINOR_VERSION >= 1)) + #ifndef PLATFORM_DESKTOP_SDL3 + #define PLATFORM_DESKTOP_SDL3 + #endif +#endif + + //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -227,6 +238,190 @@ static const int CursorsLUT[] = { //SDL_SYSTEM_CURSOR_WAITARROW, // No equivalent implemented on MouseCursor enum on raylib.h }; + +// SDL3 Migration Layer made to avoid `ifdefs` inside functions when we can. +#ifdef PLATFORM_DESKTOP_SDL3 + +// SDL3 Migration: +// SDL_WINDOW_FULLSCREEN_DESKTOP has been removed, +// and you can call SDL_GetWindowFullscreenMode() +// to see whether an exclusive fullscreen mode will be used +// or the borderless fullscreen desktop mode will be used +#define SDL_WINDOW_FULLSCREEN_DESKTOP SDL_WINDOW_FULLSCREEN + + +#define SDL_IGNORE false +#define SDL_DISABLE false +#define SDL_ENABLE true + +// SDL3 Migration: SDL_INIT_TIMER - no longer needed before calling SDL_AddTimer() +#define SDL_INIT_TIMER 0x0 // It's a flag, so no problem in setting it to zero if we use in a bitor (|) + +// SDL3 Migration: The SDL_WINDOW_SHOWN flag has been removed. Windows are shown by default and can be created hidden by using the SDL_WINDOW_HIDDEN flag. +#define SDL_WINDOW_SHOWN 0x0 // It's a flag, so no problem in setting it to zero if we use in a bitor (|) + +// +// SDL3 Migration: Renamed +// IMPORTANT: +// Might need to call SDL_CleanupEvent somewhere see :https://github.com/libsdl-org/SDL/issues/3540#issuecomment-1793449852 +// +#define SDL_DROPFILE SDL_EVENT_DROP_FILE + + +const char* SDL_GameControllerNameForIndex(int joystickIndex) +{ + // NOTE: SDL3 uses the IDs itself (SDL_JoystickID) instead of SDL2 joystick_index + const char* name = NULL; + int numJoysticks = 0; + SDL_JoystickID *joysticks = SDL_GetJoysticks(&numJoysticks); + if (joysticks) { + if (joystickIndex < numJoysticks) { + SDL_JoystickID instance_id = joysticks[joystickIndex]; + name = SDL_GetGamepadNameForID(instance_id); + } + SDL_free(joysticks); + } + return name; +} + +int SDL_GetNumVideoDisplays(void) +{ + int monitorCount = 0; + SDL_DisplayID *displays = SDL_GetDisplays(&monitorCount); + // Safe because If `mem` is NULL, SDL_free does nothing. + SDL_free(displays); + + return monitorCount; +} + + +// SLD3 Migration: +// To emulate SDL2 this function should return `SDL_DISABLE` or `SDL_ENABLE` +// representing the *processing state* of the event before this function makes any changes to it. +Uint8 SDL_EventState(Uint32 type, int state) { + + Uint8 stateBefore = SDL_EventEnabled(type); + switch (state) + { + case SDL_DISABLE: SDL_SetEventEnabled(type, false); break; + case SDL_ENABLE: SDL_SetEventEnabled(type, true); break; + default: TRACELOG(LOG_WARNING, "Event sate: unknow type"); + } + return stateBefore; +} + +void SDL_GetCurrentDisplayMode_Adapter(SDL_DisplayID displayID, SDL_DisplayMode* mode) +{ + const SDL_DisplayMode* currMode = SDL_GetCurrentDisplayMode(displayID); + if (currMode == NULL) + { + TRACELOG(LOG_WARNING, "No current display mode"); + } + else + { + *mode = *currMode; + } +} + +// SDL3 Migration: Renamed +#define SDL_GetCurrentDisplayMode SDL_GetCurrentDisplayMode_Adapter + + +SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) +{ + return SDL_CreateSurface(width, height, SDL_GetPixelFormatForMasks(depth, Rmask, Gmask, Bmask, Amask)); +} + +// SDL3 Migration: +// SDL_GetDisplayDPI() - +// not reliable across platforms, approximately replaced by multiplying +// SDL_GetWindowDisplayScale() times 160 on iPhone and Android, and 96 on other platforms. +// returns 0 on success or a negative error code on failure +int SDL_GetDisplayDPI(int displayIndex, float * ddpi, float * hdpi, float * vdpi) { + float dpi = SDL_GetWindowDisplayScale(platform.window) * 96.0; + if (ddpi != NULL) *ddpi = dpi; + if (hdpi != NULL) *hdpi = dpi; + if (vdpi != NULL) *vdpi = dpi; + return 0; +} + +SDL_Surface *SDL_CreateRGBSurfaceWithFormat(Uint32 flags, int width, int height, int depth, Uint32 format) +{ + return SDL_CreateSurface(width, height, format); +} + +SDL_Surface *SDL_CreateRGBSurfaceFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) +{ + return SDL_CreateSurfaceFrom(width, height, SDL_GetPixelFormatForMasks(depth, Rmask, Gmask, Bmask, Amask), pixels, pitch); +} + +SDL_Surface *SDL_CreateRGBSurfaceWithFormatFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 format) +{ + return SDL_CreateSurfaceFrom(width, height, format, pixels, pitch); +} + +int SDL_NumJoysticks(void) +{ + int numJoysticks; + SDL_JoystickID *joysticks = SDL_GetJoysticks(&numJoysticks); + SDL_free(joysticks); + return numJoysticks; +} + + +// SDL_SetRelativeMouseMode +// returns 0 on success or a negative error code on failure +// If relative mode is not supported, this returns -1. +int SDL_SetRelativeMouseMode_Adapter(SDL_bool enabled) +{ + + // + // SDL_SetWindowRelativeMouseMode(SDL_Window *window, bool enabled) + // \returns true on success or false on failure; call SDL_GetError() for more + // + if (SDL_SetWindowRelativeMouseMode(platform.window, enabled)) + { + return 0; // success + } + else + { + return -1; // failure + } +} + +#define SDL_SetRelativeMouseMode SDL_SetRelativeMouseMode_Adapter + +bool SDL_GetRelativeMouseMode_Adapter(void) +{ + return SDL_GetWindowRelativeMouseMode(platform.window); +} + +#define SDL_GetRelativeMouseMode SDL_GetRelativeMouseMode_Adapter + + +int SDL_GetNumTouchFingers(SDL_TouchID touchID) +{ + // SDL_Finger **SDL_GetTouchFingers(SDL_TouchID touchID, int *count) + int count = 0; + SDL_Finger **fingers = SDL_GetTouchFingers(touchID, &count); + SDL_free(fingers); + return count; +} + +#else // We're on SDL2 + +// Since SDL2 doesn't have this function we leave a stub +// SDL_GetClipboardData function is available since SDL 3.1.3. (e.g. SDL3) +void* SDL_GetClipboardData(const char *mime_type, size_t *size) { + TRACELOG(LOG_WARNING, "Getting clipboard data that is not text is only available in SDL3"); + // We could possibly implement it ourselves in this case for some easier platforms + return NULL; +} + +#endif // PLATFORM_DESKTOP_SDL3 + + + //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- @@ -256,7 +451,12 @@ void ToggleFullscreen(void) { const int monitor = SDL_GetWindowDisplayIndex(platform.window); const int monitorCount = SDL_GetNumVideoDisplays(); + +#ifdef PLATFORM_DESKTOP_SDL3 // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) { @@ -279,7 +479,11 @@ void ToggleBorderlessWindowed(void) { const int monitor = SDL_GetWindowDisplayIndex(platform.window); const int monitorCount = SDL_GetNumVideoDisplays(); +#ifdef PLATFORM_DESKTOP_SDL3 // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { if ((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0) { @@ -328,7 +532,11 @@ void SetWindowState(unsigned int flags) { const int monitor = SDL_GetWindowDisplayIndex(platform.window); const int monitorCount = SDL_GetNumVideoDisplays(); + #ifdef PLATFORM_DESKTOP_SDL3 // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) + #else if ((monitor >= 0) && (monitor < monitorCount)) + #endif { SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN); CORE.Window.fullscreen = true; @@ -387,7 +595,11 @@ void SetWindowState(unsigned int flags) { const int monitor = SDL_GetWindowDisplayIndex(platform.window); const int monitorCount = SDL_GetNumVideoDisplays(); + #ifdef PLATFORM_DESKTOP_SDL3 // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) + #else if ((monitor >= 0) && (monitor < monitorCount)) + #endif { SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN_DESKTOP); } @@ -606,7 +818,11 @@ void SetWindowMonitor(int monitor) const int screenWidth = CORE.Window.screen.width; const int screenHeight = CORE.Window.screen.height; SDL_Rect usableBounds; + #ifdef PLATFORM_DESKTOP_SDL3 // Different style for success checking + if (SDL_GetDisplayUsableBounds(monitor, &usableBounds)) + #else if (SDL_GetDisplayUsableBounds(monitor, &usableBounds) == 0) + #endif { if (wasFullscreen == 1) ToggleFullscreen(); // Leave fullscreen. @@ -704,6 +920,7 @@ int GetCurrentMonitor(void) { int currentMonitor = 0; + // Be aware that this returns an ID in SDL3 and a Index in SDL2 currentMonitor = SDL_GetWindowDisplayIndex(platform.window); return currentMonitor; @@ -716,7 +933,11 @@ Vector2 GetMonitorPosition(int monitor) if ((monitor >= 0) && (monitor < monitorCount)) { SDL_Rect displayBounds; + #ifdef PLATFORM_DESKTOP_SDL3 + if (SDL_GetDisplayUsableBounds(monitor, &displayBounds)) + #else if (SDL_GetDisplayUsableBounds(monitor, &displayBounds) == 0) + #endif { return (Vector2){ (float)displayBounds.x, (float)displayBounds.y }; } @@ -844,10 +1065,16 @@ Vector2 GetWindowScaleDPI(void) { Vector2 scale = { 1.0f, 1.0f }; +#ifndef PLATFORM_DESKTOP_SDL3 // NOTE: SDL_GetWindowDisplayScale was only added on SDL3 // see https://wiki.libsdl.org/SDL3/SDL_GetWindowDisplayScale // TODO: Implement the window scale factor calculation manually. TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); +#else + scale.x = SDL_GetWindowDisplayScale(platform.window); + scale.y = scale.x; + TRACELOG(LOG_INFO, "WindowScaleDPI is %f", scale.x); +#endif return scale; } @@ -877,19 +1104,68 @@ const char *GetClipboardText(void) return buffer; } + +#if defined(SUPPORT_CLIPBOARD_IMAGE) +// Get clipboard image +Image GetClipboardImage(void) +{ + // Let's hope compiler put these arrays in static memory + const char *image_formats[] = { + "image/bmp", + "image/png", + "image/jpg", + "image/tiff", + }; + const char *image_extensions[] = { + ".bmp", + ".png", + ".jpg", + ".tiff", + }; + + + Image image = {0}; + size_t dataSize = 0; + void *fileData = NULL; + for (int i = 0; i < SDL_arraysize(image_formats); ++i) + { + // NOTE: This pointer should be free with SDL_free() at some point. + fileData = SDL_GetClipboardData(image_formats[i], &dataSize); + if (fileData) { + image = LoadImageFromMemory(image_extensions[i], fileData, dataSize); + if (IsImageValid(image)) + { + TRACELOG(LOG_INFO, "Clipboard image: Got image from clipboard as a `%s` successfully", image_extensions[i]); + return image; + } + } + } + + TRACELOG(LOG_WARNING, "Clipboard image: Couldn't get clipboard data. %s", SDL_GetError()); + return image; +} +#endif + + // Show mouse cursor void ShowCursor(void) { +#ifdef PLATFORM_DESKTOP_SDL3 + SDL_ShowCursor(); +#else SDL_ShowCursor(SDL_ENABLE); - +#endif CORE.Input.Mouse.cursorHidden = false; } // Hides mouse cursor void HideCursor(void) { +#ifdef PLATFORM_DESKTOP_SDL3 + SDL_HideCursor(); +#else SDL_ShowCursor(SDL_DISABLE); - +#endif CORE.Input.Mouse.cursorHidden = true; } @@ -897,7 +1173,13 @@ void HideCursor(void) void EnableCursor(void) { SDL_SetRelativeMouseMode(SDL_FALSE); + +#ifdef PLATFORM_DESKTOP_SDL3 + // SDL_ShowCursor() has been split into three functions: SDL_ShowCursor(), SDL_HideCursor(), and SDL_CursorVisible() + SDL_ShowCursor(); +#else SDL_ShowCursor(SDL_ENABLE); +#endif platform.cursorRelative = false; CORE.Input.Mouse.cursorHidden = false; @@ -993,6 +1275,22 @@ const char *GetKeyName(int key) static void UpdateTouchPointsSDL(SDL_TouchFingerEvent event) { +#ifdef PLATFORM_DESKTOP_SDL3 // SDL3 + int count = 0; + SDL_Finger **fingers = SDL_GetTouchFingers(event.touchID, &count); + CORE.Input.Touch.pointCount = count; + + for (int i = 0; i < CORE.Input.Touch.pointCount; i++) + { + SDL_Finger *finger = fingers[i]; + CORE.Input.Touch.pointId[i] = finger->id; + CORE.Input.Touch.position[i].x = finger->x*CORE.Window.screen.width; + CORE.Input.Touch.position[i].y = finger->y*CORE.Window.screen.height; + CORE.Input.Touch.currentTouchState[i] = 1; + } + SDL_free(fingers); +#else // SDL2 + CORE.Input.Touch.pointCount = SDL_GetNumTouchFingers(event.touchId); for (int i = 0; i < CORE.Input.Touch.pointCount; i++) @@ -1003,6 +1301,7 @@ static void UpdateTouchPointsSDL(SDL_TouchFingerEvent event) CORE.Input.Touch.position[i].y = finger->y*CORE.Window.screen.height; CORE.Input.Touch.currentTouchState[i] = 1; } +#endif for (int i = CORE.Input.Touch.pointCount; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.currentTouchState[i] = 0; } @@ -1094,16 +1393,26 @@ void PollInputEvents(void) CORE.Window.dropFilepaths = (char **)RL_CALLOC(1024, sizeof(char *)); CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + #ifdef PLATFORM_DESKTOP_SDL3 + // const char *data; /**< The text for SDL_EVENT_DROP_TEXT and the file name for SDL_EVENT_DROP_FILE, NULL for other events */ + // Event memory is now managed by SDL, so you should not free the data in SDL_EVENT_DROP_FILE, and if you want to hold onto the text in SDL_EVENT_TEXT_EDITING and SDL_EVENT_TEXT_INPUT events, you should make a copy of it. SDL_TEXTINPUTEVENT_TEXT_SIZE is no longer necessary and has been removed. + strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.data); + #else strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.file); SDL_free(event.drop.file); + #endif CORE.Window.dropFileCount++; } else if (CORE.Window.dropFileCount < 1024) { CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + #ifdef PLATFORM_DESKTOP_SDL3 + strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.data); + #else strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.file); SDL_free(event.drop.file); + #endif CORE.Window.dropFileCount++; } @@ -1112,10 +1421,18 @@ void PollInputEvents(void) } break; // Window events are also polled (Minimized, maximized, close...) + + #ifndef PLATFORM_DESKTOP_SDL3 + // SDL3 states: + // The SDL_WINDOWEVENT_* events have been moved to top level events, + // and SDL_WINDOWEVENT has been removed. + // In general, handling this change just means checking for the individual events instead of first checking for SDL_WINDOWEVENT + // and then checking for window events. You can compare the event >= SDL_EVENT_WINDOW_FIRST and <= SDL_EVENT_WINDOW_LAST if you need to see whether it's a window event. case SDL_WINDOWEVENT: { switch (event.window.event) { + #endif case SDL_WINDOWEVENT_RESIZED: case SDL_WINDOWEVENT_SIZE_CHANGED: { @@ -1143,14 +1460,23 @@ void PollInputEvents(void) case SDL_WINDOWEVENT_FOCUS_GAINED: case SDL_WINDOWEVENT_MAXIMIZED: case SDL_WINDOWEVENT_RESTORED: + #ifdef PLATFORM_DESKTOP_SDL3 + break; + #else default: break; } } break; + #endif // Keyboard events case SDL_KEYDOWN: { + #ifdef PLATFORM_DESKTOP_SDL3 + // SDL3 Migration: The following structures have been removed: * SDL_Keysym + KeyboardKey key = ConvertScancodeToKey(event.key.scancode); + #else KeyboardKey key = ConvertScancodeToKey(event.key.keysym.scancode); + #endif if (key != KEY_NULL) { @@ -1175,7 +1501,12 @@ void PollInputEvents(void) case SDL_KEYUP: { + + #ifdef PLATFORM_DESKTOP_SDL3 + KeyboardKey key = ConvertScancodeToKey(event.key.scancode); + #else KeyboardKey key = ConvertScancodeToKey(event.key.keysym.scancode); + #endif if (key != KEY_NULL) CORE.Input.Keyboard.currentKeyState[key] = 0; } break; @@ -1527,7 +1858,11 @@ int InitPlatform(void) } // Init window +#ifdef PLATFORM_DESKTOP_SDL3 + platform.window = SDL_CreateWindow(CORE.Window.title, CORE.Window.screen.width, CORE.Window.screen.height, flags); +#else platform.window = SDL_CreateWindow(CORE.Window.title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, CORE.Window.screen.width, CORE.Window.screen.height, flags); +#endif // Init OpenGL context platform.glContext = SDL_GL_CreateContext(platform.window); @@ -1611,7 +1946,12 @@ int InitPlatform(void) CORE.Storage.basePath = SDL_GetBasePath(); // Alternative: GetWorkingDirectory(); //---------------------------------------------------------------------------- + +#ifdef PLATFORM_DESKTOP_SDL3 + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (SDL3): Initialized successfully"); +#else TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (SDL): Initialized successfully"); +#endif return 0; } diff --git a/src/raylib.h b/src/raylib.h index e0822bb7..02b3ee73 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1011,6 +1011,7 @@ RLAPI Vector2 GetWindowScaleDPI(void); // Get window RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor RLAPI void SetClipboardText(const char *text); // Set clipboard text content RLAPI const char *GetClipboardText(void); // Get clipboard text content +RLAPI Image GetClipboardImage(void); // Get clipboard image RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling diff --git a/src/rcore.c b/src/rcore.c index 75b9bf45..2d1808c1 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -512,6 +512,12 @@ const char *TextFormat(const char *text, ...); // Formatting of tex #define PLATFORM_DESKTOP_GLFW #endif +#if defined(SUPPORT_CLIPBOARD_IMAGE) + #if !defined(SUPPORT_FILEFORMAT_BMP) || !defined(STBI_REQUIRED) || !defined(SUPPORT_MODULE_RTEXTURES) + #error "To enabled SUPPORT_CLIPBOARD_IMAGE, it also needs SUPPORT_FILEFORMAT_BMP, SUPPORT_MODULE_RTEXTURES and STBI_REQUIRED to be defined. It should have been defined earlier" + #endif +#endif // SUPPORT_CLIPBOARD_IMAGE + // Include platform-specific submodules #if defined(PLATFORM_DESKTOP_GLFW) #include "platforms/rcore_desktop_glfw.c"