Browse Source

[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
Everton Jr. 9 months ago
parent
commit
00396e3436

+ 12 - 6
examples/Makefile

@@ -87,6 +87,8 @@ USE_EXTERNAL_GLFW     ?= FALSE
 # WARNING: Library is not included in raylib, it MUST be configured by users
 # WARNING: Library is not included in raylib, it MUST be configured by users
 SDL_INCLUDE_PATH      ?= $(RAYLIB_SRC_PATH)/external/SDL2/include
 SDL_INCLUDE_PATH      ?= $(RAYLIB_SRC_PATH)/external/SDL2/include
 SDL_LIBRARY_PATH      ?= $(RAYLIB_SRC_PATH)/external/SDL2/lib
 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)
 # 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
 # NOTE: This variable is only used for PLATFORM_OS: LINUX
@@ -263,9 +265,9 @@ endif
 # Define include paths for required headers: INCLUDE_PATHS
 # Define include paths for required headers: INCLUDE_PATHS
 # NOTE: Some external/extras libraries could be required (stb, easings...)
 # 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
 # Define additional directories containing required header files
+
 ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW)
 ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW)
     ifeq ($(PLATFORM_OS),BSD)
     ifeq ($(PLATFORM_OS),BSD)
         INCLUDE_PATHS += -I$(RAYLIB_INCLUDE_PATH) -I/usr/pkg/include -I/usr/X11R7/include
         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 ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL)
     ifeq ($(PLATFORM_OS),WINDOWS)
     ifeq ($(PLATFORM_OS),WINDOWS)
         # Libraries for Windows desktop compilation
         # Libraries for Windows desktop compilation
-        LDLIBS = -lraylib -lSDL2 -lSDL2main -lopengl32 -lgdi32
+        LDLIBS = -lraylib $(SDL_LIBRARIES) -lopengl32 -lgdi32
     endif
     endif
     ifeq ($(PLATFORM_OS),LINUX)
     ifeq ($(PLATFORM_OS),LINUX)
         # Libraries for Debian GNU/Linux desktop compiling
         # Libraries for Debian GNU/Linux desktop compiling
         # NOTE: Required packages: libegl1-mesa-dev
         # 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
         # On X11 requires also below libraries
         LDLIBS += -lX11
         LDLIBS += -lX11
@@ -646,8 +648,12 @@ OTHERS = \
     others/embedded_files_loading \
     others/embedded_files_loading \
     others/raylib_opengl_interop \
     others/raylib_opengl_interop \
     others/raymath_vector_angle \
     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))
 CURRENT_MAKEFILE = $(lastword $(MAKEFILE_LIST))
 
 

+ 4 - 2
src/Makefile

@@ -118,6 +118,8 @@ GLFW_LINUX_ENABLE_X11      ?= TRUE
 # WARNING: Library is not included in raylib, it MUST be configured by users
 # WARNING: Library is not included in raylib, it MUST be configured by users
 SDL_INCLUDE_PATH      ?= $(RAYLIB_SRC_PATH)/external/SDL2/include
 SDL_INCLUDE_PATH      ?= $(RAYLIB_SRC_PATH)/external/SDL2/include
 SDL_LIBRARY_PATH      ?= $(RAYLIB_SRC_PATH)/external/SDL2/lib
 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)
 # 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")
 # "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
 # Define include paths for required headers: INCLUDE_PATHS
 # NOTE: Several external required libraries (stb and others)
 # NOTE: Several external required libraries (stb and others)
 #------------------------------------------------------------------------------------------------
 #------------------------------------------------------------------------------------------------
-INCLUDE_PATHS = -I. 
+INCLUDE_PATHS = -I. $(EXTRA_INCLUDE_PATHS)
 
 
 # Define additional directories containing required header files
 # Define additional directories containing required header files
 ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW)
 ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW)
@@ -586,7 +588,7 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL)
             LDLIBS += -lX11
             LDLIBS += -lX11
         endif
         endif
     endif
     endif
-    LDLIBS += -lSDL2 -lSDL2main
+    LDLIBS += $(SDL_LIBRARIES)
 endif
 endif
 ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_RGFW)
 ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_RGFW)
     ifeq ($(PLATFORM_OS),WINDOWS)
     ifeq ($(PLATFORM_OS),WINDOWS)

+ 28 - 0
src/config.h

@@ -71,6 +71,7 @@
 // Enabling this flag allows manual control of the frame processes, use at your own risk
 // Enabling this flag allows manual control of the frame processes, use at your own risk
 //#define SUPPORT_CUSTOM_FRAME_CONTROL    1
 //#define SUPPORT_CUSTOM_FRAME_CONTROL    1
 
 
+
 // rcore: Configuration values
 // rcore: Configuration values
 //------------------------------------------------------------------------------------
 //------------------------------------------------------------------------------------
 #define MAX_FILEPATH_CAPACITY        8192       // Maximum file paths capacity
 #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
 #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
 #endif // CONFIG_H

+ 374 - 0
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 <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <assert.h>
+
+// 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. <minwindef.h>) 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 <sdkddkver.h>
+// #include <windows.h>
+// #include <winuser.h>
+#include <minwindef.h>
+// #include <minwinbase.h>
+
+#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
+

+ 32 - 0
src/platforms/rcore_desktop_glfw.c

@@ -58,6 +58,7 @@
 #if defined(_WIN32)
 #if defined(_WIN32)
     typedef void *PVOID;
     typedef void *PVOID;
     typedef PVOID HANDLE;
     typedef PVOID HANDLE;
+    #include "../external/win32_clipboard.h"
     typedef HANDLE HWND;
     typedef HANDLE HWND;
     #define GLFW_EXPOSE_NATIVE_WIN32
     #define GLFW_EXPOSE_NATIVE_WIN32
     #define GLFW_NATIVE_INCLUDE_NONE // To avoid some symbols re-definition in windows.h
     #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);
     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
 // Show mouse cursor
 void ShowCursor(void)
 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
 // EOF

+ 37 - 0
src/platforms/rcore_desktop_rgfw.c

@@ -664,6 +664,43 @@ const char *GetClipboardText(void)
     return RGFW_readClipboard(NULL);
     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
 // Show mouse cursor
 void ShowCursor(void)
 void ShowCursor(void)
 {
 {

+ 343 - 3
src/platforms/rcore_desktop_sdl.c

@@ -23,7 +23,7 @@
 *           Custom flag for rcore on target platform -not used-
 *           Custom flag for rcore on target platform -not used-
 *
 *
 *   DEPENDENCIES:
 *   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)
 *       - 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)
 #include "SDL.h"                // SDL base library (window/rendered, input, timing... functionality)
 
 
 #if defined(GRAPHICS_API_OPENGL_ES2)
 #if defined(GRAPHICS_API_OPENGL_ES2)
@@ -64,6 +68,13 @@
     #define MAX_CLIPBOARD_BUFFER_LENGTH 1024 // Size of the clipboard buffer used on GetClipboardText()
     #define MAX_CLIPBOARD_BUFFER_LENGTH 1024 // Size of the clipboard buffer used on GetClipboardText()
 #endif
 #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
 // 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
     //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
 // Module Internal Functions Declaration
 //----------------------------------------------------------------------------------
 //----------------------------------------------------------------------------------
@@ -256,7 +451,12 @@ void ToggleFullscreen(void)
 {
 {
     const int monitor = SDL_GetWindowDisplayIndex(platform.window);
     const int monitor = SDL_GetWindowDisplayIndex(platform.window);
     const int monitorCount = SDL_GetNumVideoDisplays();
     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))
     if ((monitor >= 0) && (monitor < monitorCount))
+#endif
     {
     {
         if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0)
         if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0)
         {
         {
@@ -279,7 +479,11 @@ void ToggleBorderlessWindowed(void)
 {
 {
     const int monitor = SDL_GetWindowDisplayIndex(platform.window);
     const int monitor = SDL_GetWindowDisplayIndex(platform.window);
     const int monitorCount = SDL_GetNumVideoDisplays();
     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))
     if ((monitor >= 0) && (monitor < monitorCount))
+#endif
     {
     {
         if ((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0)
         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 monitor = SDL_GetWindowDisplayIndex(platform.window);
         const int monitorCount = SDL_GetNumVideoDisplays();
         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))
         if ((monitor >= 0) && (monitor < monitorCount))
+    #endif
         {
         {
             SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN);
             SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN);
             CORE.Window.fullscreen = true;
             CORE.Window.fullscreen = true;
@@ -387,7 +595,11 @@ void SetWindowState(unsigned int flags)
     {
     {
         const int monitor = SDL_GetWindowDisplayIndex(platform.window);
         const int monitor = SDL_GetWindowDisplayIndex(platform.window);
         const int monitorCount = SDL_GetNumVideoDisplays();
         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))
         if ((monitor >= 0) && (monitor < monitorCount))
+    #endif
         {
         {
             SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
             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 screenWidth = CORE.Window.screen.width;
         const int screenHeight = CORE.Window.screen.height;
         const int screenHeight = CORE.Window.screen.height;
         SDL_Rect usableBounds;
         SDL_Rect usableBounds;
+    #ifdef PLATFORM_DESKTOP_SDL3 // Different style for success checking
+        if (SDL_GetDisplayUsableBounds(monitor, &usableBounds))
+    #else
         if (SDL_GetDisplayUsableBounds(monitor, &usableBounds) == 0)
         if (SDL_GetDisplayUsableBounds(monitor, &usableBounds) == 0)
+    #endif
         {
         {
             if (wasFullscreen == 1) ToggleFullscreen(); // Leave fullscreen.
             if (wasFullscreen == 1) ToggleFullscreen(); // Leave fullscreen.
 
 
@@ -704,6 +920,7 @@ int GetCurrentMonitor(void)
 {
 {
     int currentMonitor = 0;
     int currentMonitor = 0;
 
 
+    // Be aware that this returns an ID in SDL3 and a Index in SDL2
     currentMonitor = SDL_GetWindowDisplayIndex(platform.window);
     currentMonitor = SDL_GetWindowDisplayIndex(platform.window);
 
 
     return currentMonitor;
     return currentMonitor;
@@ -716,7 +933,11 @@ Vector2 GetMonitorPosition(int monitor)
     if ((monitor >= 0) && (monitor < monitorCount))
     if ((monitor >= 0) && (monitor < monitorCount))
     {
     {
         SDL_Rect displayBounds;
         SDL_Rect displayBounds;
+    #ifdef PLATFORM_DESKTOP_SDL3
+        if (SDL_GetDisplayUsableBounds(monitor, &displayBounds))
+    #else
         if (SDL_GetDisplayUsableBounds(monitor, &displayBounds) == 0)
         if (SDL_GetDisplayUsableBounds(monitor, &displayBounds) == 0)
+    #endif
         {
         {
             return (Vector2){ (float)displayBounds.x, (float)displayBounds.y };
             return (Vector2){ (float)displayBounds.x, (float)displayBounds.y };
         }
         }
@@ -844,10 +1065,16 @@ Vector2 GetWindowScaleDPI(void)
 {
 {
     Vector2 scale = { 1.0f, 1.0f };
     Vector2 scale = { 1.0f, 1.0f };
 
 
+#ifndef PLATFORM_DESKTOP_SDL3
     // NOTE: SDL_GetWindowDisplayScale was only added on SDL3
     // NOTE: SDL_GetWindowDisplayScale was only added on SDL3
     //       see https://wiki.libsdl.org/SDL3/SDL_GetWindowDisplayScale
     //       see https://wiki.libsdl.org/SDL3/SDL_GetWindowDisplayScale
     // TODO: Implement the window scale factor calculation manually.
     // TODO: Implement the window scale factor calculation manually.
     TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform");
     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;
     return scale;
 }
 }
@@ -877,19 +1104,68 @@ const char *GetClipboardText(void)
     return buffer;
     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
 // Show mouse cursor
 void ShowCursor(void)
 void ShowCursor(void)
 {
 {
+#ifdef PLATFORM_DESKTOP_SDL3
+    SDL_ShowCursor();
+#else
     SDL_ShowCursor(SDL_ENABLE);
     SDL_ShowCursor(SDL_ENABLE);
-
+#endif
     CORE.Input.Mouse.cursorHidden = false;
     CORE.Input.Mouse.cursorHidden = false;
 }
 }
 
 
 // Hides mouse cursor
 // Hides mouse cursor
 void HideCursor(void)
 void HideCursor(void)
 {
 {
+#ifdef PLATFORM_DESKTOP_SDL3
+    SDL_HideCursor();
+#else
     SDL_ShowCursor(SDL_DISABLE);
     SDL_ShowCursor(SDL_DISABLE);
-
+#endif
     CORE.Input.Mouse.cursorHidden = true;
     CORE.Input.Mouse.cursorHidden = true;
 }
 }
 
 
@@ -897,7 +1173,13 @@ void HideCursor(void)
 void EnableCursor(void)
 void EnableCursor(void)
 {
 {
     SDL_SetRelativeMouseMode(SDL_FALSE);
     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);
     SDL_ShowCursor(SDL_ENABLE);
+#endif
 
 
     platform.cursorRelative = false;
     platform.cursorRelative = false;
     CORE.Input.Mouse.cursorHidden = false;
     CORE.Input.Mouse.cursorHidden = false;
@@ -993,6 +1275,22 @@ const char *GetKeyName(int key)
 
 
 static void UpdateTouchPointsSDL(SDL_TouchFingerEvent event)
 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);
     CORE.Input.Touch.pointCount = SDL_GetNumTouchFingers(event.touchId);
 
 
     for (int i = 0; i < CORE.Input.Touch.pointCount; i++)
     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.position[i].y = finger->y*CORE.Window.screen.height;
         CORE.Input.Touch.currentTouchState[i] = 1;
         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;
     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 = (char **)RL_CALLOC(1024, sizeof(char *));
 
 
                     CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, 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);
                     strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.file);
                     SDL_free(event.drop.file);
                     SDL_free(event.drop.file);
+                #endif
 
 
                     CORE.Window.dropFileCount++;
                     CORE.Window.dropFileCount++;
                 }
                 }
                 else if (CORE.Window.dropFileCount < 1024)
                 else if (CORE.Window.dropFileCount < 1024)
                 {
                 {
                     CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char));
                     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);
                     strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.file);
                     SDL_free(event.drop.file);
                     SDL_free(event.drop.file);
+                #endif
 
 
                     CORE.Window.dropFileCount++;
                     CORE.Window.dropFileCount++;
                 }
                 }
@@ -1112,10 +1421,18 @@ void PollInputEvents(void)
             } break;
             } break;
 
 
             // Window events are also polled (Minimized, maximized, close...)
             // 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:
             case SDL_WINDOWEVENT:
             {
             {
                 switch (event.window.event)
                 switch (event.window.event)
                 {
                 {
+        #endif
                     case SDL_WINDOWEVENT_RESIZED:
                     case SDL_WINDOWEVENT_RESIZED:
                     case SDL_WINDOWEVENT_SIZE_CHANGED:
                     case SDL_WINDOWEVENT_SIZE_CHANGED:
                     {
                     {
@@ -1143,14 +1460,23 @@ void PollInputEvents(void)
                     case SDL_WINDOWEVENT_FOCUS_GAINED:
                     case SDL_WINDOWEVENT_FOCUS_GAINED:
                     case SDL_WINDOWEVENT_MAXIMIZED:
                     case SDL_WINDOWEVENT_MAXIMIZED:
                     case SDL_WINDOWEVENT_RESTORED:
                     case SDL_WINDOWEVENT_RESTORED:
+            #ifdef PLATFORM_DESKTOP_SDL3
+                        break;
+            #else
                     default: break;
                     default: break;
                 }
                 }
             } break;
             } break;
+            #endif
 
 
             // Keyboard events
             // Keyboard events
             case SDL_KEYDOWN:
             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);
                 KeyboardKey key = ConvertScancodeToKey(event.key.keysym.scancode);
+            #endif
 
 
                 if (key != KEY_NULL)
                 if (key != KEY_NULL)
                 {
                 {
@@ -1175,7 +1501,12 @@ void PollInputEvents(void)
 
 
             case SDL_KEYUP:
             case SDL_KEYUP:
             {
             {
+
+            #ifdef PLATFORM_DESKTOP_SDL3
+                KeyboardKey key = ConvertScancodeToKey(event.key.scancode);
+            #else
                 KeyboardKey key = ConvertScancodeToKey(event.key.keysym.scancode);
                 KeyboardKey key = ConvertScancodeToKey(event.key.keysym.scancode);
+            #endif
                 if (key != KEY_NULL) CORE.Input.Keyboard.currentKeyState[key] = 0;
                 if (key != KEY_NULL) CORE.Input.Keyboard.currentKeyState[key] = 0;
             } break;
             } break;
 
 
@@ -1527,7 +1858,11 @@ int InitPlatform(void)
     }
     }
 
 
     // Init window
     // 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);
     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
     // Init OpenGL context
     platform.glContext = SDL_GL_CreateContext(platform.window);
     platform.glContext = SDL_GL_CreateContext(platform.window);
@@ -1611,7 +1946,12 @@ int InitPlatform(void)
     CORE.Storage.basePath = SDL_GetBasePath(); // Alternative: GetWorkingDirectory();
     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");
     TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (SDL): Initialized successfully");
+#endif
 
 
     return 0;
     return 0;
 }
 }

+ 1 - 0
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 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 void SetClipboardText(const char *text);                    // Set clipboard text content
 RLAPI const char *GetClipboardText(void);                         // Get 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 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
 RLAPI void DisableEventWaiting(void);                             // Disable waiting for events on EndDrawing(), automatic events polling
 
 

+ 6 - 0
src/rcore.c

@@ -512,6 +512,12 @@ const char *TextFormat(const char *text, ...);              // Formatting of tex
     #define PLATFORM_DESKTOP_GLFW
     #define PLATFORM_DESKTOP_GLFW
 #endif
 #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
 // Include platform-specific submodules
 #if defined(PLATFORM_DESKTOP_GLFW)
 #if defined(PLATFORM_DESKTOP_GLFW)
     #include "platforms/rcore_desktop_glfw.c"
     #include "platforms/rcore_desktop_glfw.c"