SDL: camera: Add an optional property that reports if a camera is back or front.

From 8db2a3b27a98054e4ac5f0bd23bd5c3d336cd90c Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 7 Feb 2024 09:17:01 -0500
Subject: [PATCH] camera: Add an optional property that reports if a camera is
 back or front.

This is useful for iOS and Android, so an app can find the camera it cares
about in the list of devices.
---
 include/SDL3/SDL_camera.h                   | 12 +++++++++
 include/SDL3/SDL_properties.h               |  3 +++
 src/camera/coremedia/SDL_camera_coremedia.m | 28 ++++++++++++++++-----
 3 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/include/SDL3/SDL_camera.h b/include/SDL3/SDL_camera.h
index c070666273d70..57c41a130aa46 100644
--- a/include/SDL3/SDL_camera.h
+++ b/include/SDL3/SDL_camera.h
@@ -305,6 +305,16 @@ extern DECLSPEC SDL_CameraDeviceID SDLCALL SDL_GetCameraInstanceID(SDL_Camera *c
 /**
  * Get the properties associated with an opened camera.
  *
+ * The following read-only properties are provided by SDL:
+ *
+ * - `SDL_PROP_CAMERA_POSITION_STRING`: the position of the camera in
+ *   relation to the hardware it is connected to. This is currently either
+ *   the string "front" or "back", to signify which side of the user's
+ *   device a camera is on. Future versions of SDL may add other position
+ *   strings. This property is only set if this information can be
+ *   determined by SDL. Most platforms do not set this attribute at all,
+ *   but mobile devices tend to.
+ *
  * \param camera the SDL_Camera obtained from SDL_OpenCameraDevice()
  * \returns a valid property ID on success or 0 on failure; call
  *          SDL_GetError() for more information.
@@ -318,6 +328,8 @@ extern DECLSPEC SDL_CameraDeviceID SDLCALL SDL_GetCameraInstanceID(SDL_Camera *c
  */
 extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetCameraProperties(SDL_Camera *camera);
 
+#define SDL_PROP_CAMERA_POSITION_STRING "SDL.camera.position"
+
 /**
  * Get the spec that a camera is using when generating images.
  *
diff --git a/include/SDL3/SDL_properties.h b/include/SDL3/SDL_properties.h
index 8061972373c39..f2fda2810b8d2 100644
--- a/include/SDL3/SDL_properties.h
+++ b/include/SDL3/SDL_properties.h
@@ -183,6 +183,9 @@ extern DECLSPEC int SDLCALL SDL_SetProperty(SDL_PropertiesID props, const char *
 /**
  * Set a string property on a set of properties
  *
+ * This function makes a copy of the string; the caller does not have to
+ * preserve the data after this call completes.
+ *
  * \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/src/camera/coremedia/SDL_camera_coremedia.m b/src/camera/coremedia/SDL_camera_coremedia.m
index cf07d47cce7a6..4d2e40f351f1f 100644
--- a/src/camera/coremedia/SDL_camera_coremedia.m
+++ b/src/camera/coremedia/SDL_camera_coremedia.m
@@ -386,20 +386,36 @@ static SDL_bool FindCoreMediaCameraDeviceByUniqueID(SDL_CameraDevice *device, vo
     return ([uniqueid isEqualToString:avdev.uniqueID]) ? SDL_TRUE : SDL_FALSE;
 }
 
-static void MaybeAddDevice(AVCaptureDevice *device)
+static void MaybeAddDevice(AVCaptureDevice *avdevice)
 {
-    if (!device.connected) {
+    if (!avdevice.connected) {
         return;  // not connected.
-    } else if (![device hasMediaType:AVMediaTypeVideo]) {
+    } else if (![avdevice hasMediaType:AVMediaTypeVideo]) {
         return;  // not a camera.
-    } else if (SDL_FindPhysicalCameraDeviceByCallback(FindCoreMediaCameraDeviceByUniqueID, (__bridge void *) device.uniqueID)) {
+    } else if (SDL_FindPhysicalCameraDeviceByCallback(FindCoreMediaCameraDeviceByUniqueID, (__bridge void *) avdevice.uniqueID)) {
         return;  // already have this one.
     }
 
     CameraFormatAddData add_data;
-    GatherCameraSpecs(device, &add_data);
+    GatherCameraSpecs(avdevice, &add_data);
     if (add_data.num_specs > 0) {
-        SDL_AddCameraDevice(device.localizedName.UTF8String, add_data.num_specs, add_data.specs, (void *) CFBridgingRetain(device));
+        SDL_CameraDevice *device = SDL_AddCameraDevice(avdevice.localizedName.UTF8String, add_data.num_specs, add_data.specs, (void *) CFBridgingRetain(avdevice));
+        if (device) {
+            const char *posstr = NULL;
+            if (avdevice.position == AVCaptureDevicePositionFront) {
+                posstr = "front";
+            } else if (avdevice.position == AVCaptureDevicePositionBack) {
+                posstr = "back";
+            }
+
+            if (posstr) {
+                SDL_Camera *camera = (SDL_Camera *) device;  // currently there's no separation between physical and logical device.
+                SDL_PropertiesID props = SDL_GetCameraProperties(camera);
+                if (props) {
+                    SDL_SetStringProperty(props, SDL_PROP_CAMERA_POSITION_STRING, posstr);
+                }
+            }
+        }
     }
     SDL_free(add_data.specs);
 }