SDL: Re-added a simplified version of SDL_SetWindowShape()

From f6b92c9b88bbb11612c2e006c1ac33792f4255d0 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 9 Feb 2024 07:09:59 -0800
Subject: [PATCH] Re-added a simplified version of SDL_SetWindowShape()

In order to handle mouse click transparency this needs to be implemented inside SDL
---
 VisualC-GDK/SDL/SDL.vcxproj                   |      2 +
 VisualC-GDK/SDL/SDL.vcxproj.filters           |      2 +
 VisualC/SDL/SDL.vcxproj                       |      2 +
 VisualC/SDL/SDL.vcxproj.filters               |      6 +
 docs/README-migration.md                      |      2 +-
 include/SDL3/SDL_properties.h                 |      2 +
 include/SDL3/SDL_video.h                      |     19 +
 src/SDL_properties.c                          |     12 +
 src/SDL_properties_c.h                        |      1 +
 src/dynapi/SDL_dynapi.sym                     |      1 +
 src/dynapi/SDL_dynapi_overrides.h             |      1 +
 src/dynapi/SDL_dynapi_procs.h                 |      1 +
 src/render/SDL_render.c                       |     42 +-
 src/render/SDL_sysrender.h                    |      5 +
 src/video/SDL_sysvideo.h                      |      1 +
 src/video/SDL_video.c                         |     41 +
 src/video/cocoa/SDL_cocoawindow.h             |      1 +
 src/video/cocoa/SDL_cocoawindow.m             |     26 +
 src/video/dummy/SDL_nullframebuffer.c         |      9 +-
 src/video/n3ds/SDL_n3dsframebuffer.c          |      9 +-
 .../offscreen/SDL_offscreenframebuffer.c      |      9 +-
 src/video/windows/SDL_windowsshape.c          |    124 +
 src/video/windows/SDL_windowsshape.h          |     28 +
 src/video/windows/SDL_windowsvideo.c          |      1 +
 src/video/windows/SDL_windowsvideo.h          |      1 +
 src/video/x11/SDL_x11shape.c                  |    110 +
 src/video/x11/SDL_x11shape.h                  |     28 +
 src/video/x11/SDL_x11sym.h                    |      1 +
 src/video/x11/SDL_x11video.c                  |      2 +
 test/CMakeLists.txt                           |      3 +-
 test/glass.bmp                                |    Bin 0 -> 1537738 bytes
 test/glass.h                                  | 128148 +++++++++++++++
 test/testshape.c                              |     95 +-
 33 files changed, 128676 insertions(+), 59 deletions(-)
 create mode 100644 src/video/windows/SDL_windowsshape.c
 create mode 100644 src/video/windows/SDL_windowsshape.h
 create mode 100644 src/video/x11/SDL_x11shape.c
 create mode 100644 src/video/x11/SDL_x11shape.h
 create mode 100644 test/glass.bmp
 create mode 100644 test/glass.h

diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index c064a77f9504..e070c31ef76e 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -565,6 +565,7 @@
     <ClInclude Include="..\..\src\video\windows\SDL_windowsmouse.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsopengl.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsopengles.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsshape.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsvideo.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsvulkan.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowswindow.h" />
@@ -822,6 +823,7 @@
     <ClCompile Include="..\..\src\video\windows\SDL_windowsmouse.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsopengl.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsopengles.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsshape.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvideo.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvulkan.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowswindow.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 2767954b1f74..f5d648fe70cb 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -198,6 +198,7 @@
     <ClCompile Include="..\..\src\video\windows\SDL_windowsmouse.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsopengl.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsopengles.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsshape.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvideo.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvulkan.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowswindow.c" />
@@ -423,6 +424,7 @@
     <ClInclude Include="..\..\src\video\windows\SDL_windowsmouse.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsopengl.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsopengles.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsshape.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsvideo.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsvulkan.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowswindow.h" />
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 4c0bea8a13ba..45415212dcfd 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -459,6 +459,7 @@
     <ClInclude Include="..\..\src\video\windows\SDL_windowsmouse.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsopengl.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsopengles.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsshape.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsvideo.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowsvulkan.h" />
     <ClInclude Include="..\..\src\video\windows\SDL_windowswindow.h" />
@@ -668,6 +669,7 @@
     <ClCompile Include="..\..\src\video\windows\SDL_windowsmouse.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsopengl.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsopengles.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsshape.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvideo.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvulkan.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowswindow.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 6971749d59b5..8073004813fd 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -645,6 +645,9 @@
     <ClInclude Include="..\..\src\video\windows\SDL_windowsopengl.h">
       <Filter>video\windows</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsshape.h">
+      <Filter>video\windows</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\video\windows\SDL_windowsvideo.h">
       <Filter>video\windows</Filter>
     </ClInclude>
@@ -1231,6 +1234,9 @@
     <ClCompile Include="..\..\src\video\windows\SDL_windowsopengles.c">
       <Filter>video\windows</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsshape.c">
+      <Filter>video\windows</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvideo.c">
       <Filter>video\windows</Filter>
     </ClCompile>
diff --git a/docs/README-migration.md b/docs/README-migration.md
index 9642df3c01da..da7a7bda8c0e 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -1305,7 +1305,7 @@ The following functions have been removed:
 
 ## SDL_shape.h
 
-This header has been removed. You can create a window with the SDL_WINDOW_TRANSPARENT flag and then render using the alpha channel to achieve a similar effect. You can see an example of this in test/testshape.c
+This header has been removed and a simplified version of this API has been added as SDL_SetWindowShape() in SDL_video.h. See test/testshape.c for an example.
 
 ## SDL_stdinc.h
 
diff --git a/include/SDL3/SDL_properties.h b/include/SDL3/SDL_properties.h
index 498d8e5beceb..0a2870b1ccb5 100644
--- a/include/SDL3/SDL_properties.h
+++ b/include/SDL3/SDL_properties.h
@@ -141,6 +141,8 @@ extern DECLSPEC void SDLCALL SDL_UnlockProperties(SDL_PropertiesID props);
  * Set a property on a set of properties with a cleanup function that is
  * called when the property is deleted
  *
+ * The cleanup function is also called if setting the property fails for any reason.
+ *
  * \param props the properties to modify
  * \param name the name of the property to modify
  * \param value the new value of the property, or NULL to delete the property
diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h
index c8a68dbb1b80..808aeea33232 100644
--- a/include/SDL3/SDL_video.h
+++ b/include/SDL3/SDL_video.h
@@ -1028,6 +1028,8 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_GetWindowParent(SDL_Window *window);
  *
  * The following read-only properties are provided by SDL:
  *
+ * - `SDL_PROP_WINDOW_SHAPE_POINTER`: the surface associated with a shaped window
+ *
  * On Android:
  *
  * - `SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER`: the ANativeWindow associated
@@ -1120,6 +1122,7 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_GetWindowParent(SDL_Window *window);
  */
 extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window *window);
 
+#define SDL_PROP_WINDOW_SHAPE_POINTER                   "SDL.window.shape"
 #define SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER          "SDL.window.android.window"
 #define SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER         "SDL.window.android.surface"
 #define SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER            "SDL.window.uikit.window"
@@ -2119,6 +2122,22 @@ typedef SDL_HitTestResult (SDLCALL *SDL_HitTest)(SDL_Window *win,
  */
 extern DECLSPEC int SDLCALL SDL_SetWindowHitTest(SDL_Window *window, SDL_HitTest callback, void *callback_data);
 
+/**
+ * Set the shape of a transparent window.
+ *
+ * This sets the alpha channel of a transparent window and any fully transparent areas are also transparent to mouse clicks. If you are using something besides the SDL render API, then you are responsible for setting the alpha channel of the window yourself.
+ *
+ * The window must have been created with the SDL_WINDOW_TRANSPARENT flag.
+ *
+ * \param window the window
+ * \param shape the surface representing the shape of the window, or NULL to remove any current shape
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_SetWindowShape(SDL_Window *window, SDL_Surface *shape);
+
 /**
  * Request a window to demand attention from the user.
  *
diff --git a/src/SDL_properties.c b/src/SDL_properties.c
index 50cb2cd682d4..566ea51ba970 100644
--- a/src/SDL_properties.c
+++ b/src/SDL_properties.c
@@ -348,6 +348,7 @@ int SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *v
 
     property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
     if (!property) {
+        SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE);
         return -1;
     }
     property->type = SDL_PROPERTY_TYPE_POINTER;
@@ -374,6 +375,17 @@ int SDL_SetProperty(SDL_PropertiesID props, const char *name, void *value)
     return SDL_PrivateSetProperty(props, name, property);
 }
 
+static void CleanupSurface(void *userdata, void *value)
+{
+    SDL_Surface *surface = (SDL_Surface *)value;
+
+    SDL_DestroySurface(surface);
+}
+
+int SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface)
+{
+    return SDL_SetPropertyWithCleanup(props, name, surface, CleanupSurface, NULL);
+}
 
 int SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value)
 {
diff --git a/src/SDL_properties_c.h b/src/SDL_properties_c.h
index 9b590cde3dad..ee23ae8e8b15 100644
--- a/src/SDL_properties_c.h
+++ b/src/SDL_properties_c.h
@@ -20,4 +20,5 @@
 */
 
 extern int SDL_InitProperties(void);
+extern int SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface);
 extern void SDL_QuitProperties(void);
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 20ee328d9bb0..5bf8b931563b 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -970,6 +970,7 @@ SDL3_0.0.0 {
     SDL_SetRenderColorScale;
     SDL_GetRenderColorScale;
     SDL_RenderGeometryRawFloat;
+    SDL_SetWindowShape;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 64939646e3c8..ac097b09f5ea 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -995,3 +995,4 @@
 #define SDL_SetRenderColorScale SDL_SetRenderColorScale_REAL
 #define SDL_GetRenderColorScale SDL_GetRenderColorScale_REAL
 #define SDL_RenderGeometryRawFloat SDL_RenderGeometryRawFloat_REAL
+#define SDL_SetWindowShape SDL_SetWindowShape_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 2a32ddf5ece0..81d1c92966d4 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1020,3 +1020,4 @@ SDL_DYNAPI_PROC(int,SDL_CopyProperties,(SDL_PropertiesID a, SDL_PropertiesID b),
 SDL_DYNAPI_PROC(int,SDL_SetRenderColorScale,(SDL_Renderer *a, float b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_GetRenderColorScale,(SDL_Renderer *a, float *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_RenderGeometryRawFloat,(SDL_Renderer *a, SDL_Texture *b, const float *c, int d, const SDL_FColor *e, int f, const float *g, int h, int i, const void *j, int k, int l),(a,b,c,d,e,f,g,h,i,j,k,l),return)
+SDL_DYNAPI_PROC(int,SDL_SetWindowShape,(SDL_Window *a, SDL_Surface *b),(a,b),return)
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 09101e328a9c..03464ef46211 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -989,10 +989,14 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
 
     renderer->color_scale = 1.0f;
 
-    if (SDL_GetWindowFlags(window) & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) {
-        renderer->hidden = SDL_TRUE;
-    } else {
-        renderer->hidden = SDL_FALSE;
+    if (window) {
+        if (SDL_GetWindowFlags(window) & SDL_WINDOW_TRANSPARENT) {
+            renderer->transparent_window = SDL_TRUE;
+        }
+
+        if (SDL_GetWindowFlags(window) & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) {
+            renderer->hidden = SDL_TRUE;
+        }
     }
 
     new_props = SDL_GetRendererProperties(renderer);
@@ -4247,6 +4251,32 @@ SDL_Surface *SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)
     return renderer->RenderReadPixels(renderer, &real_rect);
 }
 
+static void SDL_RenderApplyWindowShape(SDL_Renderer *renderer)
+{
+    SDL_Surface *shape = (SDL_Surface *)SDL_GetProperty(SDL_GetWindowProperties(renderer->window), SDL_PROP_WINDOW_SHAPE_POINTER, NULL);
+    if (shape != renderer->shape_surface) {
+        if (renderer->shape_texture) {
+            SDL_DestroyTexture(renderer->shape_texture);
+            renderer->shape_texture = NULL;
+        }
+
+        if (shape) {
+            /* There's nothing we can do if this fails, so just keep on going */
+            renderer->shape_texture = SDL_CreateTextureFromSurface(renderer, shape);
+
+            SDL_SetTextureBlendMode(renderer->shape_texture,
+                SDL_ComposeCustomBlendMode(
+                    SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_ADD,
+                    SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_ADD));
+        }
+        renderer->shape_surface = shape;
+    }
+
+    if (renderer->shape_texture) {
+        SDL_RenderTexture(renderer, renderer->shape_texture, NULL, NULL);
+    }
+}
+
 static void SDL_SimulateRenderVSync(SDL_Renderer *renderer)
 {
     Uint64 now, elapsed;
@@ -4285,6 +4315,10 @@ int SDL_RenderPresent(SDL_Renderer *renderer)
         SDL_RenderLogicalPresentation(renderer);
     }
 
+    if (renderer->transparent_window) {
+        SDL_RenderApplyWindowShape(renderer);
+    }
+
     FlushRenderCommands(renderer); /* time to send everything to the GPU! */
 
 #if DONT_DRAW_WHILE_HIDDEN
diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h
index 838f8749081f..8d12b5d271f2 100644
--- a/src/render/SDL_sysrender.h
+++ b/src/render/SDL_sysrender.h
@@ -277,6 +277,11 @@ struct SDL_Renderer
     size_t vertex_data_used;
     size_t vertex_data_allocation;
 
+    /* Shaped window support */
+    SDL_bool transparent_window;
+    SDL_Surface *shape_surface;
+    SDL_Texture *shape_texture;
+
     SDL_PropertiesID props;
 
     void *driverdata;
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index 9bf5f3e6b9dd..68b609603358 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -253,6 +253,7 @@ struct SDL_VideoDevice
     int (*UpdateWindowFramebuffer)(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects);
     void (*DestroyWindowFramebuffer)(SDL_VideoDevice *_this, SDL_Window *window);
     void (*OnWindowEnter)(SDL_VideoDevice *_this, SDL_Window *window);
+    int (*UpdateWindowShape)(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *shape);
     int (*FlashWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);
     int (*SetWindowFocusable)(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable);
     int (*SyncWindow)(SDL_VideoDevice *_this, SDL_Window *window);
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 5ad2126acd62..cbc6b29e1d00 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -31,6 +31,7 @@
 #include "SDL_video_c.h"
 #include "../events/SDL_events_c.h"
 #include "../SDL_hints_c.h"
+#include "../SDL_properties_c.h"
 #include "../timer/SDL_timer_c.h"
 #include "SDL_video_capture_c.h"
 
@@ -3485,6 +3486,13 @@ void SDL_OnWindowResized(SDL_Window *window)
 {
     SDL_CheckWindowDisplayChanged(window);
     SDL_CheckWindowPixelSizeChanged(window);
+
+    if ((window->flags & SDL_WINDOW_TRANSPARENT) && _this->UpdateWindowShape) {
+        SDL_Surface *surface = (SDL_Surface *)SDL_GetProperty(window->props, SDL_PROP_WINDOW_SHAPE_POINTER, NULL);
+        if (surface) {
+            _this->UpdateWindowShape(_this, window, surface);
+        }
+    }
 }
 
 void SDL_CheckWindowPixelSizeChanged(SDL_Window *window)
@@ -5054,6 +5062,39 @@ int SDL_SetWindowHitTest(SDL_Window *window, SDL_HitTest callback, void *callbac
     return 0;
 }
 
+int SDL_SetWindowShape(SDL_Window *window, SDL_Surface *shape)
+{
+    SDL_PropertiesID props;
+    SDL_Surface *surface;
+
+    CHECK_WINDOW_MAGIC(window, -1);
+
+    if (!(window->flags & SDL_WINDOW_TRANSPARENT)) {
+        return SDL_SetError("Window must be created with SDL_WINDOW_TRANSPARENT");
+    }
+
+    props = SDL_GetWindowProperties(window);
+    if (!props) {
+        return -1;
+    }
+
+    surface = SDL_ConvertSurfaceFormat(shape, SDL_PIXELFORMAT_ARGB32);
+    if (!surface) {
+        return -1;
+    }
+
+    if (SDL_SetSurfaceProperty(props, SDL_PROP_WINDOW_SHAPE_POINTER, surface) < 0) {
+        return -1;
+    }
+
+    if (_this->UpdateWindowShape) {
+        if (_this->UpdateWindowShape(_this, window, surface) < 0) {
+            return -1;
+        }
+    }
+    return 0;
+}
+
 /*
  * Functions used by iOS application delegates
  */
diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h
index 96fec957abb5..2831ad334f94 100644
--- a/src/video/cocoa/SDL_cocoawindow.h
+++ b/src/video/cocoa/SDL_cocoawindow.h
@@ -74,6 +74,7 @@ typedef enum
 - (BOOL)isMovingOrFocusClickPending;
 - (void)setFocusClickPending:(NSInteger)button;
 - (void)clearFocusClickPending:(NSInteger)button;
+- (void)updateIgnoreMouseState:(NSEvent *)theEvent;
 - (void)setPendingMoveX:(float)x Y:(float)y;
 - (void)windowDidFinishMoving;
 - (void)onMovingOrFocusClickPendingStateCleared;
diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m
index 090558b95150..e8afbb74207b 100644
--- a/src/video/cocoa/SDL_cocoawindow.m
+++ b/src/video/cocoa/SDL_cocoawindow.m
@@ -834,6 +834,28 @@ - (void)clearFocusClickPending:(NSInteger)button
     }
 }
 
+- (void)updateIgnoreMouseState:(NSEvent *)theEvent
+{
+    SDL_Window *window = _data.window;
+    SDL_Surface *shape = (SDL_Surface *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_SHAPE_POINTER, NULL);
+    BOOL ignoresMouseEvents = NO;
+
+    if (shape) {
+        NSPoint point = [theEvent locationInWindow];
+		NSRect windowRect = [[_data.nswindow contentView] frame];
+		if (NSMouseInRect(point, windowRect, NO)) {
+			int x = (int)SDL_roundf((point.x / (window->w - 1)) * (shape->w - 1));
+			int y = (int)SDL_roundf(((window->h - point.y) / (window->h - 1)) * (shape->h - 1));
+			Uint8 a;
+
+			if (SDL_ReadSurfacePixel(shape, x, y, NULL, NULL, NULL, &a) < 0 || a == SDL_ALPHA_TRANSPARENT) {
+				ignoresMouseEvents = YES;
+			}
+		}
+    }
+    _data.nswindow.ignoresMouseEvents = ignoresMouseEvents;
+}
+
 - (void)setPendingMoveX:(float)x Y:(float)y
 {
     pendingWindowWarpX = x;
@@ -1555,6 +1577,10 @@ - (void)mouseMoved:(NSEvent *)theEvent
     mouseID = mouse->mouseID;
     window = _data.window;
 
+    if (window->flags & SDL_WINDOW_TRANSPARENT) {
+        [self updateIgnoreMouseState:theEvent];
+    }
+
     if ([self processHitTest:theEvent]) {
         SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
         return; /* dragging, drop event. */
diff --git a/src/video/dummy/SDL_nullframebuffer.c b/src/video/dummy/SDL_nullframebuffer.c
index 87d1b1be5f13..70661510914b 100644
--- a/src/video/dummy/SDL_nullframebuffer.c
+++ b/src/video/dummy/SDL_nullframebuffer.c
@@ -23,16 +23,11 @@
 #ifdef SDL_VIDEO_DRIVER_DUMMY
 
 #include "../SDL_sysvideo.h"
+#include "../../SDL_properties_c.h"
 #include "SDL_nullframebuffer_c.h"
 
 #define DUMMY_SURFACE "SDL.internal.window.surface"
 
-static void CleanupSurface(void *userdata, void *value)
-{
-    SDL_Surface *surface = (SDL_Surface *)value;
-
-    SDL_DestroySurface(surface);
-}
 
 int SDL_DUMMY_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint32 *format, void **pixels, int *pitch)
 {
@@ -48,7 +43,7 @@ int SDL_DUMMY_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window
     }
 
     /* Save the info and return! */
-    SDL_SetPropertyWithCleanup(SDL_GetWindowProperties(window), DUMMY_SURFACE, surface, CleanupSurface, NULL);
+    SDL_SetSurfaceProperty(SDL_GetWindowProperties(window), DUMMY_SURFACE, surface);
     *format = surface_format;
     *pixels = surface->pixels;
     *pitch = surface->pitch;
diff --git a/src/video/n3ds/SDL_n3dsframebuffer.c b/src/video/n3ds/SDL_n3dsframebuffer.c
index f368506feec4..0b098d2bd932 100644
--- a/src/video/n3ds/SDL_n3dsframebuffer.c
+++ b/src/video/n3ds/SDL_n3dsframebuffer.c
@@ -23,6 +23,7 @@
 #ifdef SDL_VIDEO_DRIVER_N3DS
 
 #include "../SDL_sysvideo.h"
+#include "../../SDL_properties_c.h"
 #include "SDL_n3dsframebuffer_c.h"
 #include "SDL_n3dsvideo.h"
 
@@ -38,12 +39,6 @@ static int GetDestOffset(int x, int y, int dest_width);
 static int GetSourceOffset(int x, int y, int source_width);
 static void FlushN3DSBuffer(const void *buffer, u32 bufsize, gfxScreen_t screen);
 
-static void CleanupSurface(void *userdata, void *value)
-{
-    SDL_Surface *surface = (SDL_Surface *)value;
-
-    SDL_DestroySurface(surface);
-}
 
 int SDL_N3DS_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint32 *format, void **pixels, int *pitch)
 {
@@ -57,7 +52,7 @@ int SDL_N3DS_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window,
         return -1;
     }
 
-    SDL_SetPropertyWithCleanup(SDL_GetWindowProperties(window), N3DS_SURFACE, framebuffer, CleanupSurface, NULL);
+    SDL_SetSurfaceProperty(SDL_GetWindowProperties(window), N3DS_SURFACE, framebuffer);
     *format = FRAMEBUFFER_FORMAT;
     *pixels = framebuffer->pixels;
     *pitch = framebuffer->pitch;
diff --git a/src/video/offscreen/SDL_offscreenframebuffer.c b/src/video/offscreen/SDL_offscreenframebuffer.c
index 76fa8b72774b..e712892bbda1 100644
--- a/src/video/offscreen/SDL_offscreenframebuffer.c
+++ b/src/video/offscreen/SDL_offscreenframebuffer.c
@@ -23,16 +23,11 @@
 #ifdef SDL_VIDEO_DRIVER_OFFSCREEN
 
 #include "../SDL_sysvideo.h"
+#include "../../SDL_properties_c.h"
 #include "SDL_offscreenframebuffer_c.h"
 
 #define OFFSCREEN_SURFACE "SDL.internal.window.surface"
 
-static void CleanupSurface(void *userdata, void *value)
-{
-    SDL_Surface *surface = (SDL_Surface *)value;
-
-    SDL_DestroySurface(surface);
-}
 
 int SDL_OFFSCREEN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint32 *format, void **pixels, int *pitch)
 {
@@ -48,7 +43,7 @@ int SDL_OFFSCREEN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *wi
     }
 
     /* Save the info and return! */
-    SDL_SetPropertyWithCleanup(SDL_GetWindowProperties(window), OFFSCREEN_SURFACE, surface, CleanupSurface, NULL);
+    SDL_SetSurfaceProperty(SDL_GetWindowProperties(window), OFFSCREEN_SURFACE, surface);
     *format = surface_format;
     *pixels = surface->pixels;
     *pitch = surface->pitch;
diff --git a/src/video/windows/SDL_windowsshape.c b/src/video/windows/SDL_windowsshape.c
new file mode 100644
index 000000000000..0b5cac330d40
--- /dev/null
+++ b/src/video/windows/SDL_windowsshape.c
@@ -0,0 +1,124 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+
+#include "SDL_windowsvideo.h"
+#include "SDL_windowsshape.h"
+
+
+static void AddRegion(HRGN *mask, int x1, int y1, int x2, int y2)
+{
+    HRGN region = CreateRectRgn(x1, y1, x2, y2);
+    if (*mask) {
+        CombineRgn(*mask, *mask, region, RGN_OR);
+        DeleteObject(region);
+    } else {
+        *mask = region;
+    }
+}
+
+static HRGN GenerateSpanListRegion(SDL_Surface *shape, int offset_x, int offset_y)
+{
+    HRGN mask = NULL;
+    int x, y;
+    int span_start = -1;
+
+    for (y = 0; y < shape->h; ++y) {
+        const Uint8 *a = (const Uint8 *)shape->pixels + y * shape->pitch;
+        for (x = 0; x < shape->w; ++x) {
+            if (*a == SDL_ALPHA_TRANSPARENT) {
+                if (span_start != -1) {
+                    AddRegion(&mask, offset_x + span_start, offset_y + y, offset_x + x, offset_y + y + 1);
+                    span_start = -1;
+                }
+            } else {
+                if (span_start == -1) {
+                    span_start = x;
+                }
+            }
+            a += 4;
+        }
+        if (span_start != -1) {
+            /* Add the final span */
+            AddRegion(&mask, offset_x + span_start, offset_y + y, offset_x + x, offset_y + y + 1);
+            span_start = -1;
+        }
+    }
+    return mask;
+}
+
+int WIN_UpdateWindowShape(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *shape)
+{
+    SDL_WindowData *data = window->driverdata;
+    HRGN mask = NULL;
+
+    /* Generate a set of spans for the region */
+    if (shape) {
+        SDL_Surface *stretched = NULL;
+        RECT rect;
+
+        if (shape->w != window->w || shape->h != window->h) {
+            stretched = SDL_CreateSurface(window->w, window->h, SDL_PIXELFORMAT_ARGB32);
+            if (!stretched) {
+                return -1;
+            }
+            if (SDL_SoftStretch(shape, NULL, stretched, NULL, SDL_SCALEMODE_LINEAR) < 0) {
+                SDL_DestroySurface(stretched);
+                return -1;
+            }
+            shape = stretched;
+        }
+
+        rect.top = 0;
+        rect.left = 0;
+        rect.bottom = 0;
+        rect.right = 0;
+        if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS)) {
+            WIN_AdjustWindowRectForHWND(data->hwnd, &rect, 0);
+        }
+
+        mask = GenerateSpanListRegion(shape, -rect.left, -rect.top);
+
+        if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS)) {
+            /* Add the window borders */
+            /* top */
+            AddRegion(&mask, 0, 0, -rect.left + shape->w + rect.right + 1, -rect.top + 1);
+            /* left */
+            AddRegion(&mask, 0, -rect.top, -rect.left + 1, -rect.top + shape->h + 1);
+            /* right */
+            AddRegion(&mask, -rect.left + shape->w, -rect.top, -rect.left + shape->w + rect.right + 1, -rect.top + shape->h + 1);
+            /* bottom */
+            AddRegion(&mask, 0, -rect.top + shape->h, -rect.left + shape->w + rect.right + 1, -rect.top + shape->h + rect.bottom + 1);
+        }
+
+        if (stretched) {
+            SDL_DestroySurface(stretched);
+        }
+    }
+    if (!SetWindowRgn(data->hwnd, mask, TRUE)) {
+        return WIN_SetError("SetWindowRgn failed");
+    }
+    return 0;
+}
+
+#endif /* SDL_VIDEO_DRIVER_WINDOWS */
diff --git a/src/video/windows/SDL_windowsshape.h b/src/video/windows/SDL_windowsshape.h
new file mode 100644
index 000000000000..380fe0b8ff3e
--- /dev/null
+++ b/src/video/windows/SDL_windowsshape.h
@@ -0,0 +1,28 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifndef SDL_windowsshape_h_
+#define SDL_windowsshape_h_
+
+extern int WIN_UpdateWindowShape(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *shape);
+
+#endif /* SDL_windowsshape_h_ */
diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c
index 7b4385a9c74c..afc016a36e1b 100644
--- a/src/video/windows/SDL_windowsvideo.c
+++ b/src/video/windows/SDL_windowsvideo.c
@@ -210,6 +210,7 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
     device->ShowWindowSystemMenu = WIN_ShowWindowSystemMenu;
     device->SetWindowFocusable = WIN_SetWindowFocusable;
 #endif
+    device->UpdateWi

(Patch may be truncated, please check the link at the top of this post.)