SDL: Added property types: pointer, string, number, float

From 0907f345cb36c77bfdcc9e9f9f2942ecb42824a2 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 12 Nov 2023 09:47:31 -0800
Subject: [PATCH] Added property types: pointer, string, number, float

---
 include/SDL3/SDL_properties.h                 | 171 ++++++++-
 include/SDL3/SDL_render.h                     |  38 +-
 include/SDL3/SDL_video.h                      |  54 +--
 src/SDL_properties.c                          | 345 ++++++++++++++++--
 src/core/linux/SDL_fcitx.c                    |   6 +-
 src/core/linux/SDL_ibus.c                     |   6 +-
 src/dynapi/SDL_dynapi.sym                     |   8 +
 src/dynapi/SDL_dynapi_overrides.h             |   8 +
 src/dynapi/SDL_dynapi_procs.h                 |  10 +-
 src/render/SDL_render.c                       |   2 +-
 src/render/direct3d/SDL_render_d3d.c          |   2 +-
 src/render/direct3d11/SDL_render_d3d11.c      |   2 +-
 src/render/direct3d11/SDL_render_winrt.cpp    |   2 +-
 src/render/direct3d12/SDL_render_d3d12.c      |   2 +-
 src/render/metal/SDL_render_metal.m           |  12 +-
 src/render/opengl/SDL_render_gl.c             |  10 +-
 src/render/opengles2/SDL_render_gles2.c       |   8 +-
 src/video/SDL_video.c                         |   6 +-
 src/video/cocoa/SDL_cocoawindow.m             |   2 +-
 src/video/dummy/SDL_nullframebuffer.c         |   2 +-
 src/video/kmsdrm/SDL_kmsdrmvideo.c            |   4 +-
 src/video/n3ds/SDL_n3dsframebuffer.c          |   2 +-
 .../offscreen/SDL_offscreenframebuffer.c      |   2 +-
 src/video/uikit/SDL_uikitwindow.m             |   2 +-
 src/video/x11/SDL_x11window.c                 |   4 +-
 test/testautomation_properties.c              | 134 ++++++-
 test/testautomation_video.c                   |  19 +-
 test/testffmpeg.c                             |   6 +-
 28 files changed, 727 insertions(+), 142 deletions(-)

diff --git a/include/SDL3/SDL_properties.h b/include/SDL3/SDL_properties.h
index 17b280ae19e0..655a21d41551 100644
--- a/include/SDL3/SDL_properties.h
+++ b/include/SDL3/SDL_properties.h
@@ -39,6 +39,18 @@ extern "C" {
  */
 typedef Uint32 SDL_PropertiesID;
 
+/**
+ * SDL property type
+ */
+typedef enum
+{
+    SDL_PROPERTY_TYPE_INVALID,
+    SDL_PROPERTY_TYPE_POINTER,
+    SDL_PROPERTY_TYPE_STRING,
+    SDL_PROPERTY_TYPE_NUMBER,
+    SDL_PROPERTY_TYPE_FLOAT,
+} SDL_PropertyType;
+
 /**
  * Get the global SDL properties
  *
@@ -105,6 +117,28 @@ extern DECLSPEC int SDLCALL SDL_LockProperties(SDL_PropertiesID props);
  */
 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
+ *
+ * \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
+ * \param cleanup the function to call when this property is deleted, or NULL
+ *                if no cleanup is necessary
+ * \param userdata a pointer that is passed to the cleanup function
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetProperty
+ * \sa SDL_SetProperty
+ */
+extern DECLSPEC int SDLCALL SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, void (SDLCALL *cleanup)(void *userdata, void *value), void *userdata);
+
 /**
  * Set a property on a set of properties
  *
@@ -124,15 +158,11 @@ extern DECLSPEC void SDLCALL SDL_UnlockProperties(SDL_PropertiesID props);
 extern DECLSPEC int SDLCALL SDL_SetProperty(SDL_PropertiesID props, const char *name, void *value);
 
 /**
- * Set a property on a set of properties with a cleanup function that is
- * called when the property is deleted
+ * Set a string property on a set of properties
  *
  * \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
- * \param cleanup the function to call when this property is deleted, or NULL
- *                if no cleanup is necessary
- * \param userdata a pointer that is passed to the cleanup function
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
  *
@@ -140,10 +170,56 @@ extern DECLSPEC int SDLCALL SDL_SetProperty(SDL_PropertiesID props, const char *
  *
  * \since This function is available since SDL 3.0.0.
  *
- * \sa SDL_GetProperty
- * \sa SDL_SetProperty
+ * \sa SDL_GetStringProperty
  */
-extern DECLSPEC int SDLCALL SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, void (SDLCALL *cleanup)(void *userdata, void *value), void *userdata);
+extern DECLSPEC int SDLCALL SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value);
+
+/**
+ * Set an integer property on a set of properties
+ *
+ * \param props the properties to modify
+ * \param name the name of the property to modify
+ * \param value the new value of the property
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetNumberProperty
+ */
+extern DECLSPEC int SDLCALL SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value);
+
+/**
+ * Set a floating point property on a set of properties
+ *
+ * \param props the properties to modify
+ * \param name the name of the property to modify
+ * \param value the new value of the property
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetFloatProperty
+ */
+extern DECLSPEC int SDLCALL SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value);
+
+/**
+ * Get the type of a property on a set of properties
+ *
+ * \param props the properties to query
+ * \param name the name of the property to query
+ * \returns the type of the property, or SDL_PROPERTY_TYPE_INVALID if it is not set.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC SDL_PropertyType SDLCALL SDL_GetPropertyType(SDL_PropertiesID props, const char *name);
 
 /**
  * Get a property on a set of properties
@@ -155,7 +231,8 @@ extern DECLSPEC int SDLCALL SDL_SetPropertyWithCleanup(SDL_PropertiesID props, c
  *
  * \param props the properties to query
  * \param name the name of the property to query
- * \returns the value of the property, or NULL if it is not set.
+ * \param default_value the default value of the property
+ * \returns the value of the property, or `default_value` if it is not set or not a pointer property.
  *
  * \threadsafety It is safe to call this function from any thread, although
  *               the data returned is not protected and could potentially be
@@ -165,9 +242,65 @@ extern DECLSPEC int SDLCALL SDL_SetPropertyWithCleanup(SDL_PropertiesID props, c
  *
  * \since This function is available since SDL 3.0.0.
  *
+ * \sa SDL_GetPropertyType
  * \sa SDL_SetProperty
  */
-extern DECLSPEC void *SDLCALL SDL_GetProperty(SDL_PropertiesID props, const char *name);
+extern DECLSPEC void *SDLCALL SDL_GetProperty(SDL_PropertiesID props, const char *name, void *default_value);
+
+/**
+ * Get a string property on a set of properties
+ *
+ * \param props the properties to query
+ * \param name the name of the property to query
+ * \param default_value the default value of the property
+ * \returns the value of the property, or `default_value` if it is not set or not a string property.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetPropertyType
+ * \sa SDL_SetStringProperty
+ */
+extern DECLSPEC const char *SDLCALL SDL_GetStringProperty(SDL_PropertiesID props, const char *name, const char *default_value);
+
+/**
+ * Get a number property on a set of properties
+ *
+ * You can use SDL_GetPropertyType() to query whether the property exists and is a number property.
+ *
+ * \param props the properties to query
+ * \param name the name of the property to query
+ * \param default_value the default value of the property
+ * \returns the value of the property, or `default_value` if it is not set or not a number property.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetPropertyType
+ * \sa SDL_SetNumberProperty
+ */
+extern DECLSPEC Sint64 SDLCALL SDL_GetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 default_value);
+
+/**
+ * Get a floating point property on a set of properties
+ *
+ * You can use SDL_GetPropertyType() to query whether the property exists and is a floating point property.
+ *
+ * \param props the properties to query
+ * \param name the name of the property to query
+ * \param default_value the default value of the property
+ * \returns the value of the property, or `default_value` if it is not set or not a float property.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetPropertyType
+ * \sa SDL_SetFloatProperty
+ */
+extern DECLSPEC float SDLCALL SDL_GetFloatProperty(SDL_PropertiesID props, const char *name, float default_value);
 
 /**
  * Clear a property on a set of properties
@@ -185,6 +318,24 @@ extern DECLSPEC void *SDLCALL SDL_GetProperty(SDL_PropertiesID props, const char
  */
 extern DECLSPEC int SDLCALL SDL_ClearProperty(SDL_PropertiesID props, const char *name);
 
+typedef void (SDLCALL *SDL_EnumeratePropertiesCallback)(void *userdata, SDL_PropertiesID props, const char *name);
+/**
+ * Enumerate the properties on a set of properties
+ *
+ * The callback function is called for each property on the set of properties. The properties are locked during enumeration.
+ *
+ * \param props the properties to query
+ * \param callback the function to call for each property
+ * \param userdata a pointer that is passed to `callback`
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCallback callback, void *userdata);
+
 /**
  * Destroy a set of properties
  *
diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index 120543dd436e..72cb5d263d81 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -311,10 +311,10 @@ extern DECLSPEC int SDLCALL SDL_GetRendererInfo(SDL_Renderer *renderer, SDL_Rend
  *
  * The following properties are provided by SDL:
  * ```
- * "SDL.renderer.d3d9.device" - the IDirect3DDevice9 associated with the renderer
- * "SDL.renderer.d3d11.device" - the ID3D11Device associated with the renderer
- * "SDL.renderer.d3d12.device" - the ID3D12Device associated with the renderer
- * "SDL.renderer.d3d12.command_queue" - the ID3D12CommandQueue associated with the renderer
+ * "SDL.renderer.d3d9.device" (pointer) - the IDirect3DDevice9 associated with the renderer
+ * "SDL.renderer.d3d11.device" (pointer) - the ID3D11Device associated with the renderer
+ * "SDL.renderer.d3d12.device" (pointer) - the ID3D12Device associated with the renderer
+ * "SDL.renderer.d3d12.command_queue" (pointer) - the ID3D12CommandQueue associated with the renderer
  * ```
  *
  * \param renderer the rendering context
@@ -424,33 +424,33 @@ extern DECLSPEC SDL_Texture *SDLCALL SDL_CreateTextureFromSurface(SDL_Renderer *
  *
  * With the direct3d11 renderer:
  * ```
- * "SDL.texture.d3d11.texture" - the ID3D11Texture2D associated with the texture
- * "SDL.texture.d3d11.texture_u" - the ID3D11Texture2D associated with the U plane of a YUV texture
- * "SDL.texture.d3d11.texture_v" - the ID3D11Texture2D associated with the V plane of a YUV texture
+ * "SDL.texture.d3d11.texture" (pointer) - the ID3D11Texture2D associated with the texture
+ * "SDL.texture.d3d11.texture_u" (pointer) - the ID3D11Texture2D associated with the U plane of a YUV texture
+ * "SDL.texture.d3d11.texture_v" (pointer) - the ID3D11Texture2D associated with the V plane of a YUV texture
  * ```
  *
  * With the direct3d12 renderer:
  * ```
- * "SDL.texture.d3d12.texture" - the ID3D12Resource associated with the texture
- * "SDL.texture.d3d12.texture_u" - the ID3D12Resource associated with the U plane of a YUV texture
- * "SDL.texture.d3d12.texture_v" - the ID3D12Resource associated with the V plane of a YUV texture
+ * "SDL.texture.d3d12.texture" (pointer) - the ID3D12Resource associated with the texture
+ * "SDL.texture.d3d12.texture_u" (pointer) - the ID3D12Resource associated with the U plane of a YUV texture
+ * "SDL.texture.d3d12.texture_v" (pointer) - the ID3D12Resource associated with the V plane of a YUV texture
  * ```
  *
  * With the opengl renderer:
  * ```
- * "SDL.texture.opengl.texture" - the GLuint texture associated with the texture
- * "SDL.texture.opengl.texture_u" - the GLuint texture associated with the U plane of a YUV texture
- * "SDL.texture.opengl.texture_v" - the GLuint texture associated with the V plane of a YUV texture
- * "SDL.texture.opengl.tex_w" - the 16.16 fixed point texture coordinate width of the texture
- * "SDL.texture.opengl.tex_h" - the 16.16 fixed point texture coordinate height of the texture
+ * "SDL.texture.opengl.texture" (number) - the GLuint texture associated with the texture
+ * "SDL.texture.opengl.texture_u" (number) - the GLuint texture associated with the U plane of a YUV texture
+ * "SDL.texture.opengl.texture_v" (number) - the GLuint texture associated with the V plane of a YUV texture
+ * "SDL.texture.opengl.tex_w" (float) - the texture coordinate width of the texture (0.0 - 1.0)
+ * "SDL.texture.opengl.tex_h" (float) - the texture coordinate height of the texture (0.0 - 1.0)
  * ```
  *
  * With the opengles2 renderer:
  * ```
- * "SDL.texture.opengles2.texture" - the GLuint texture associated with the texture
- * "SDL.texture.opengles2.texture_uv" - the GLuint texture associated with the UV plane of an NV12 texture
- * "SDL.texture.opengles2.texture_u" - the GLuint texture associated with the U plane of a YUV texture
- * "SDL.texture.opengles2.texture_v" - the GLuint texture associated with the V plane of a YUV texture
+ * "SDL.texture.opengles2.texture" (number) - the GLuint texture associated with the texture
+ * "SDL.texture.opengles2.texture_uv" (number) - the GLuint texture associated with the UV plane of an NV12 texture
+ * "SDL.texture.opengles2.texture_u" (number) - the GLuint texture associated with the U plane of a YUV texture
+ * "SDL.texture.opengles2.texture_v" (number) - the GLuint texture associated with the V plane of a YUV texture
  * ```
  *
  * \param texture the texture to query
diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h
index e0363eccdfaf..e8db562df843 100644
--- a/include/SDL3/SDL_video.h
+++ b/include/SDL3/SDL_video.h
@@ -938,58 +938,58 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_GetWindowParent(SDL_Window *window);
  *
  * On Android:
  * ```
- * "SDL.window.android.window" - the ANativeWindow associated with the window
- * "SDL.window.android.surface" - the EGLSurface associated with the window
+ * "SDL.window.android.window" (pointer) - the ANativeWindow associated with the window
+ * "SDL.window.android.surface" (pointer) - the EGLSurface associated with the window
  * ```
  *
  * On iOS:
  * ```
- * "SDL.window.uikit.window" - the (__unsafe_unretained) UIWindow associated with the window
- * "SDL.window.uikit.metal_view_tag" - the NSInteger tag assocated with metal views on the window
+ * "SDL.window.uikit.window" (pointer) - the (__unsafe_unretained) UIWindow associated with the window
+ * "SDL.window.uikit.metal_view_tag" (number) - the NSInteger tag assocated with metal views on the window
  * ```
  *
  * On KMS/DRM:
  * ```
- * "SDL.window.kmsdrm.dev_index" - the device index associated with the window (e.g. the X in /dev/dri/cardX)
- * "SDL.window.kmsdrm.drm_fd" - the DRM FD associated with the window
- * "SDL.window.kmsdrm.gbm_dev" - the GBM device associated with the window
+ * "SDL.window.kmsdrm.dev_index" (number) - the device index associated with the window (e.g. the X in /dev/dri/cardX)
+ * "SDL.window.kmsdrm.drm_fd" (number) - the DRM FD associated with the window
+ * "SDL.window.kmsdrm.gbm_dev" (pointer) - the GBM device associated with the window
  * ```
  *
  * On macOS:
  * ```
- * "SDL.window.cocoa.window" - the (__unsafe_unretained) NSWindow associated with the window
- * "SDL.window.cocoa.metal_view_tag" - the NSInteger tag assocated with metal views on the window
+ * "SDL.window.cocoa.window" (pointer) - the (__unsafe_unretained) NSWindow associated with the window
+ * "SDL.window.cocoa.metal_view_tag" (number) - the NSInteger tag assocated with metal views on the window
  * ```
  *
  * On Vivante:
  * ```
- * "SDL.window.vivante.display" - the EGLNativeDisplayType associated with the window
- * "SDL.window.vivante.window" - the EGLNativeWindowType associated with the window
- * "SDL.window.vivante.surface" - the EGLSurface associated with the window
+ * "SDL.window.vivante.display" (pointer) - the EGLNativeDisplayType associated with the window
+ * "SDL.window.vivante.window" (pointer) - the EGLNativeWindowType associated with the window
+ * "SDL.window.vivante.surface" (pointer) - the EGLSurface associated with the window
  * ```
  *
  * On UWP:
  * ```
- * "SDL.window.winrt.window" - the IInspectable CoreWindow associated with the window
+ * "SDL.window.winrt.window" (pointer) - the IInspectable CoreWindow associated with the window
  * ```
  *
  * On Windows:
  * ```
- * "SDL.window.win32.hwnd" - the HWND associated with the window
- * "SDL.window.win32.hdc" - the HDC associated with the window
- * "SDL.window.win32.instance" - the HINSTANCE associated with the window
+ * "SDL.window.win32.hwnd" (pointer) - the HWND associated with the window
+ * "SDL.window.win32.hdc" (pointer) - the HDC associated with the window
+ * "SDL.window.win32.instance" (pointer) - the HINSTANCE associated with the window
  * ```
  *
  * On Wayland:
  * ```
- * "SDL.window.wayland.registry" - the wl_registry associated with the window
- * "SDL.window.wayland.display" - the wl_display associated with the window
- * "SDL.window.wayland.surface" - the wl_surface associated with the window
- * "SDL.window.wayland.egl_window" - the wl_egl_window associated with the window
- * "SDL.window.wayland.xdg_surface" - the xdg_surface associated with the window
- * "SDL.window.wayland.xdg_toplevel" - the xdg_toplevel role associated with the window
- * "SDL.window.wayland.xdg_popup" - the xdg_popup role associated with the window
- * "SDL.window.wayland.xdg_positioner" - the xdg_positioner associated with the window, in popup mode
+ * "SDL.window.wayland.registry" (pointer) - the wl_registry associated with the window
+ * "SDL.window.wayland.display" (pointer) - the wl_display associated with the window
+ * "SDL.window.wayland.surface" (pointer) - the wl_surface associated with the window
+ * "SDL.window.wayland.egl_window" (pointer) - the wl_egl_window associated with the window
+ * "SDL.window.wayland.xdg_surface" (pointer) - the xdg_surface associated with the window
+ * "SDL.window.wayland.xdg_toplevel" (pointer) - the xdg_toplevel role associated with the window
+ * "SDL.window.wayland.xdg_popup" (pointer) - the xdg_popup role associated with the window
+ * "SDL.window.wayland.xdg_positioner" (pointer) - the xdg_positioner associated with the window, in popup mode
  * ```
  *
  * Note: The xdg_* window objects do not internally persist across window show/hide calls.
@@ -997,9 +997,9 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_GetWindowParent(SDL_Window *window);
  *
  * On X11:
  * ```
- * "SDL.window.x11.display" - the X11 Display associated with the window
- * "SDL.window.x11.screen" - the screen number associated with the window
- * "SDL.window.x11.window" - the X11 Window associated with the window
+ * "SDL.window.x11.display" (pointer) - the X11 Display associated with the window
+ * "SDL.window.x11.screen" (number) - the screen number associated with the window
+ * "SDL.window.x11.window" (number) - the X11 Window associated with the window
  * ```
  *
  * \param window the window to query
diff --git a/src/SDL_properties.c b/src/SDL_properties.c
index de54ee8c3009..5b47b597ac98 100644
--- a/src/SDL_properties.c
+++ b/src/SDL_properties.c
@@ -25,7 +25,15 @@
 
 typedef struct
 {
-    void *value;
+    SDL_PropertyType type;
+
+    union {
+        void *pointer_value;
+        char *string_value;
+        Sint64 number_value;
+        float float_value;
+    } value;
+
     void (SDLCALL *cleanup)(void *userdata, void *value);
     void *userdata;
 } SDL_Property;
@@ -45,8 +53,19 @@ static SDL_PropertiesID SDL_global_properties;
 static void SDL_FreeProperty(const void *key, const void *value, void *data)
 {
     SDL_Property *property = (SDL_Property *)value;
-    if (property->cleanup) {
-        property->cleanup(property->userdata, property->value);
+    if (property) {
+        switch (property->type) {
+        case SDL_PROPERTY_TYPE_POINTER:
+            if (property->cleanup) {
+                property->cleanup(property->userdata, property->value.pointer_value);
+            }
+            break;
+        case SDL_PROPERTY_TYPE_STRING:
+            SDL_free(property->value.string_value);
+            break;
+        default:
+            break;
+        }
     }
     SDL_free((void *)key);
     SDL_free((void *)value);
@@ -196,21 +215,17 @@ void SDL_UnlockProperties(SDL_PropertiesID props)
     SDL_UnlockMutex(properties->lock);
 }
 
-int SDL_SetProperty(SDL_PropertiesID props, const char *name, void *value)
-{
-    return SDL_SetPropertyWithCleanup(props, name, value, NULL, NULL);
-}
-
-int SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, void (SDLCALL *cleanup)(void *userdata, void *value), void *userdata)
+static int SDL_PrivateSetProperty(SDL_PropertiesID props, const char *name, SDL_Property *property)
 {
     SDL_Properties *properties = NULL;
-    SDL_Property *property = NULL;
     int result = 0;
 
     if (!props) {
+        SDL_FreeProperty(NULL, property, NULL);
         return SDL_InvalidParamError("props");
     }
     if (!name || !*name) {
+        SDL_FreeProperty(NULL, property, NULL);
         return SDL_InvalidParamError("name");
     }
 
@@ -219,20 +234,10 @@ int SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *v
     SDL_UnlockRWLock(SDL_properties_lock);
 
     if (!properties) {
+        SDL_FreeProperty(NULL, property, NULL);
         return SDL_InvalidParamError("props");
     }
 
-    if (value) {
-        property = (SDL_Property *)SDL_malloc(sizeof(*property));
-        if (!property) {
-            return SDL_OutOfMemory();
-        }
-
-        property->value = value;
-        property->cleanup = cleanup;
-        property->userdata = userdata;
-    }
-
     SDL_LockMutex(properties->lock);
     {
         SDL_RemoveFromHashTable(properties->props, name);
@@ -249,18 +254,135 @@ int SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *v
     return result;
 }
 
-void *SDL_GetProperty(SDL_PropertiesID props, const char *name)
+int SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, void (SDLCALL *cleanup)(void *userdata, void *value), void *userdata)
+{
+    SDL_Property *property;
+
+    if (!value) {
+        return SDL_ClearProperty(props, name);
+    }
+
+    property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
+    if (!property) {
+        return SDL_OutOfMemory();
+    }
+    property->type = SDL_PROPERTY_TYPE_POINTER;
+    property->value.pointer_value = value;
+    property->cleanup = cleanup;
+    property->userdata = userdata;
+    return SDL_PrivateSetProperty(props, name, property);
+}
+
+int SDL_SetProperty(SDL_PropertiesID props, const char *name, void *value)
+{
+    SDL_Property *property;
+
+    if (!value) {
+        return SDL_ClearProperty(props, name);
+    }
+
+    property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
+    if (!property) {
+        return SDL_OutOfMemory();
+    }
+    property->type = SDL_PROPERTY_TYPE_POINTER;
+    property->value.pointer_value = value;
+    return SDL_PrivateSetProperty(props, name, property);
+}
+
+
+int SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value)
+{
+    SDL_Property *property;
+
+    if (!value) {
+        return SDL_ClearProperty(props, name);
+    }
+
+    property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
+    if (!property) {
+        return SDL_OutOfMemory();
+    }
+    property->type = SDL_PROPERTY_TYPE_STRING;
+    property->value.string_value = SDL_strdup(value);
+    if (!property->value.string_value) {
+        SDL_free(property);
+        return SDL_OutOfMemory();
+    }
+    return SDL_PrivateSetProperty(props, name, property);
+}
+
+int SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value)
+{
+    SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
+    if (!property) {
+        return SDL_OutOfMemory();
+    }
+    property->type = SDL_PROPERTY_TYPE_NUMBER;
+    property->value.number_value = value;
+    return SDL_PrivateSetProperty(props, name, property);
+}
+
+int SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value)
+{
+    SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
+    if (!property) {
+        return SDL_OutOfMemory();
+    }
+    property->type = SDL_PROPERTY_TYPE_FLOAT;
+    property->value.float_value = value;
+    return SDL_PrivateSetProperty(props, name, property);
+}
+
+SDL_PropertyType SDL_GetPropertyType(SDL_PropertiesID props, const char *name)
+{
+    SDL_Properties *properties = NULL;
+    SDL_PropertyType type = SDL_PROPERTY_TYPE_INVALID;
+
+    if (!props) {
+        SDL_InvalidParamError("props");
+        return SDL_PROPERTY_TYPE_INVALID;
+    }
+    if (!name || !*name) {
+        SDL_InvalidParamError("name");
+        return SDL_PROPERTY_TYPE_INVALID;
+    }
+
+    SDL_LockRWLockForReading(SDL_properties_lock);
+    SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
+    SDL_UnlockRWLock(SDL_properties_lock);
+
+    if (!properties) {
+        SDL_InvalidParamError("props");
+        return SDL_PROPERTY_TYPE_INVALID;
+    }
+
+    SDL_LockMutex(properties->lock);
+    {
+        SDL_Property *property = NULL;
+        if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
+            type = property->type;
+        } else {
+            SDL_SetError("Couldn't find property named %s", name);
+        }
+    }
+    SDL_UnlockMutex(properties->lock);
+
+    return type;
+}
+
+void *SDL_GetProperty(SDL_PropertiesID props, const char *name, void *default_value)
 {
     SDL_Properties *properties = NULL;
-    void *value = NULL;
+    void *value = default_value;
 
     if (!props) {
         SDL_InvalidParamError("props");
-        return NULL;
+        return value;
     }
     if (!name || !*name) {
         SDL_InvalidParamError("name");
-        return NULL;
+        return value;
     }
 
     SDL_LockRWLockForReading(SDL_properties_lock);
@@ -269,7 +391,7 @@ void *SDL_GetProperty(SDL_PropertiesID props, const char *name)
 
     if (!properties) {
         SDL_InvalidParamError("props");
-        return NULL;
+        return value;
     }
 
     /* Note that taking the lock here only guarantees that we won't read the
@@ -280,7 +402,140 @@ void *SDL_GetProperty(SDL_PropertiesID props, const char *name)
     {
         SDL_Property *property = NULL;
         if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
-            value = property->value;
+            if (property->type == SDL_PROPERTY_TYPE_POINTER) {
+                value = property->value.pointer_value;
+            } else {
+                SDL_SetError("Property %s isn't a pointer value", name);
+            }
+        } else {
+            SDL_SetError("Couldn't find property named %s", name);
+        }
+    }
+    SDL_UnlockMutex(properties->lock);
+
+    return value;
+}
+
+const char *SDL_GetStringProperty(SDL_PropertiesID props, const char *name, const char *default_value)
+{
+    SDL_Properties *properties = NULL;
+    const char *value = default_value;
+
+    if (!props) {
+        SDL_InvalidParamError("props");
+        return value;
+    }
+    if (!name || !*name) {
+        SDL_InvalidParamError("name");
+        return value;
+    }
+
+    SDL_LockRWLockForReading(SDL_properties_lock);
+    SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
+    SDL_UnlockRWLock(SDL_properties_lock);
+
+    if (!properties) {
+        SDL_InvalidParamError("props");
+        return value;
+    }
+
+    /* Note that taking the lock here only guarantees that we won't read the
+     * hashtable while it's being modified. The value itself can easily be
+     * freed from another thread after it is returned here.
+     *
+     * FIXME: Should we SDL_strdup() the return value to avoid this?
+     */
+    SDL_LockMutex(properties->lock);
+    {
+        SDL_Property *property = NULL;
+        if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
+            if (property->type == SDL_PROPERTY_TYPE_STRING) {
+                value = property->value.string_value;
+            } else {
+                SDL_SetError("Property %s isn't a string value", name);
+            }
+        } else {
+            SDL_SetError("Couldn't find property named %s", name);
+        }
+    }
+    SDL_UnlockMutex(properties->lock);
+
+    return value;
+}
+
+Sint64 SDL_GetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 default_value)
+{
+    SDL_Properties *properties = NULL;
+    Sint64 value = default_value;
+
+    if (!props) {
+        SDL_InvalidParamError("props");
+        return value;
+    }
+    if (!name || !*name) {
+        SDL_InvalidParamError("name");
+        return value;
+    }
+
+    SDL_LockRWLockForReading(SDL_properties_lock);
+    SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
+    SDL_UnlockRWLock(SDL_properties_lock);
+
+    if (!properties) {
+        SDL_InvalidParamError("props");
+        return value;
+    }
+
+    SDL_LockMutex(properties->lock);
+    {
+        SDL_Property *property = NULL;
+        if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
+            if (property->type == SDL_PROPERTY_TYPE_NUMBER) {
+                value = property->value.number_value;
+            } else {
+                SDL_SetError("Property %s isn't a string value", name);
+            }
+        } else {
+            SDL_SetError("Couldn't find property named %s", name);
+       

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