Bläddra i källkod

Added SDL_SetAppMetadata() (#10404)

Removed duplicate hints SDL_HINT_APP_NAME, SDL_HINT_APP_ID, and
SDL_HINT_AUDIO_DEVICE_APP_NAME.

Wired up a few things to use the metadata; more to come!

Fixes https://github.com/libsdl-org/SDL/issues/4703
Sam Lantinga 8 månader sedan
förälder
incheckning
a36fe632fd

+ 3 - 1
docs/README-migration.md

@@ -782,10 +782,12 @@ The following hints have been removed:
 * SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING - SDL now properly handles the 0x406D1388 Exception if no debugger intercepts it, preventing its propagation.
 * SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4 - replaced with SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, defaulting to SDL_TRUE
 * SDL_HINT_XINPUT_USE_OLD_JOYSTICK_MAPPING
+* SDL_HINT_APP_NAME - replaced by either using the appname param to SDL_SetAppMetadata() or setting SDL_PROP_APP_METADATA_NAME_STRING with SDL_SetAppMetadataProperty().
+* SDL_HINT_AUDIO_DEVICE_APP_NAME - replaced by either using the appname param to SDL_SetAppMetadata() or using SDL_PROP_APP_METADATA_NAME_STRING in SDL_SetAppMetadataWithProperties()
 
 * Renamed hints SDL_HINT_VIDEODRIVER and SDL_HINT_AUDIODRIVER to SDL_HINT_VIDEO_DRIVER and SDL_HINT_AUDIO_DRIVER
 * Renamed environment variables SDL_VIDEODRIVER and SDL_AUDIODRIVER to SDL_VIDEO_DRIVER and SDL_AUDIO_DRIVER
-* The environment variables SDL_VIDEO_X11_WMCLASS and SDL_VIDEO_WAYLAND_WMCLASS have been removed and replaced with the unified hint SDL_HINT_APP_ID
+* The environment variables SDL_VIDEO_X11_WMCLASS and SDL_VIDEO_WAYLAND_WMCLASS have been removed and replaced with app metadata (either use the appindentifier param to SDL_SetAppMetadata() or set SDL_PROP_APP_METADATA_IDENTIFIER_STRING with SDL_SetAppMetadataProperty()).
 
 The following hints have been renamed:
 * SDL_HINT_ALLOW_TOPMOST => SDL_HINT_WINDOW_ALLOW_TOPMOST

+ 0 - 96
include/SDL3/SDL_hints.h

@@ -125,79 +125,6 @@ extern "C" {
  */
 #define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
 
-/**
- * A variable setting the app ID string.
- *
- * This string is used by desktop compositors to identify and group windows
- * together, as well as match applications with associated desktop settings
- * and icons.
- *
- * On Wayland this corresponds to the "app ID" window property and on X11 this
- * corresponds to the WM_CLASS property. Windows inherit the value of this
- * hint at creation time. Changing this hint after a window has been created
- * will not change the app ID or class of existing windows.
- *
- * For *nix platforms, this string should be formatted in reverse-DNS notation
- * and follow some basic rules to be valid:
- *
- * - The application ID must be composed of two or more elements separated by
- *   a period (.) character.
- * - Each element must contain one or more of the alphanumeric characters
- *   (A-Z, a-z, 0-9) plus underscore (_) and hyphen (-) and must not start
- *   with a digit. Note that hyphens, while technically allowed, should not be
- *   used if possible, as they are not supported by all components that use
- *   the ID, such as D-Bus. For maximum compatibility, replace hyphens with an
- *   underscore.
- * - The empty string is not a valid element (ie: your application ID may not
- *   start or end with a period and it is not valid to have two periods in a
- *   row).
- * - The entire ID must be less than 255 characters in length.
- *
- * Examples of valid app ID strings:
- *
- * - org.MyOrg.MyApp
- * - com.your_company.your_app
- *
- * Desktops such as GNOME and KDE require that the app ID string matches your
- * application's .desktop file name (e.g. if the app ID string is
- * 'org.MyOrg.MyApp', your application's .desktop file should be named
- * 'org.MyOrg.MyApp.desktop').
- *
- * If you plan to package your application in a container such as Flatpak, the
- * app ID should match the name of your Flatpak container as well.
- *
- * If not set, SDL will attempt to use the application executable name. If the
- * executable name cannot be retrieved, the generic string "SDL_App" will be
- * used.
- *
- * This hint should be set before SDL is initialized.
- *
- * \since This hint is available since SDL 3.0.0.
- */
-#define SDL_HINT_APP_ID      "SDL_APP_ID"
-
-/**
- * Specify an application name.
- *
- * This hint lets you specify the application name sent to the OS when
- * required. For example, this will often appear in volume control applets for
- * audio streams, and in lists of applications which are inhibiting the
- * screensaver. You should use a string that describes your program ("My Game
- * 2: The Revenge")
- *
- * Setting this to "" or leaving it unset will have SDL use a reasonable
- * default: probably the application's name or "SDL Application" if SDL
- * doesn't have any better information.
- *
- * Note that, for audio streams, this can be overridden with
- * SDL_HINT_AUDIO_DEVICE_APP_NAME.
- *
- * This hint should be set before SDL is initialized.
- *
- * \since This hint is available since SDL 3.0.0.
- */
-#define SDL_HINT_APP_NAME "SDL_APP_NAME"
-
 /**
  * A variable controlling whether controllers used with the Apple TV generate
  * UI events.
@@ -254,29 +181,6 @@ extern "C" {
  */
 #define SDL_HINT_AUDIO_CATEGORY   "SDL_AUDIO_CATEGORY"
 
-/**
- * Specify an application name for an audio device.
- *
- * Some audio backends (such as PulseAudio) allow you to describe your audio
- * stream. Among other things, this description might show up in a system
- * control panel that lets the user adjust the volume on specific audio
- * streams instead of using one giant master volume slider.
- *
- * This hints lets you transmit that information to the OS. The contents of
- * this hint are used while opening an audio device. You should use a string
- * that describes your program ("My Game 2: The Revenge")
- *
- * Setting this to "" or leaving it unset will have SDL use a reasonable
- * default: this will be the name set with SDL_HINT_APP_NAME, if that hint is
- * set. Otherwise, it'll probably the application's name or "SDL Application"
- * if SDL doesn't have any better information.
- *
- * This hint should be set before an audio device is opened.
- *
- * \since This hint is available since SDL 3.0.0.
- */
-#define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME"
-
 /**
  * Specify an application icon name for an audio device.
  *

+ 124 - 0
include/SDL3/SDL_init.h

@@ -105,12 +105,18 @@ typedef Uint32 SDL_InitFlags;
  * call SDL_Quit() to force shutdown). If a subsystem is already loaded then
  * this call will increase the ref-count and return.
  *
+ * Consider reporting some basic metadata about your application before
+ * calling SDL_Init, using either SDL_SetAppMetadata() or
+ * SDL_SetAppMetadataProperty().
+ *
  * \param flags subsystem initialization flags.
  * \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.
  *
+ * \sa SDL_SetAppMetadata
+ * \sa SDL_SetAppMetadataProperty
  * \sa SDL_InitSubSystem
  * \sa SDL_Quit
  * \sa SDL_SetMainReady
@@ -182,6 +188,124 @@ extern SDL_DECLSPEC SDL_InitFlags SDLCALL SDL_WasInit(SDL_InitFlags flags);
  */
 extern SDL_DECLSPEC void SDLCALL SDL_Quit(void);
 
+/**
+ * Specify basic metadata about your app.
+ *
+ * You can optionally provide metadata about your app to SDL. This is not
+ * required, but strongly encouraged.
+ *
+ * There are several locations where SDL can make use of metadata (an "About"
+ * box in the macOS menu bar, the name of the app can be shown on some audio
+ * mixers, etc). Any piece of metadata can be left as NULL, if a specific detail
+ * doesn't make sense for the app.
+ *
+ * This function should be called as early as possible, before SDL_Init.
+ * Multiple calls to this function are allowed, but various state might not
+ * change once it has been set up with a previous call to this function.
+ *
+ * Passing a NULL removes any previous metadata.
+ *
+ * This is a simplified interface for the most important information. You can
+ * supply significantly more detailed metadata with SDL_SetAppMetadataProperty().
+ *
+ * \param appname The name of the application ("My Game 2: Bad Guy's Revenge!").
+ * \param appversion The version of the application ("1.0.0beta5" or a git hash, or whatever makes sense).
+ * \param appidentifier A unique string in reverse-domain format that identifies this app ("com.example.mygame2").
+ * \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.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \sa SDL_SetAppMetadataProperty
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAppMetadata(const char *appname, const char *appversion, const char *appidentifier);
+
+/**
+ * Specify metadata about your app through a set of properties.
+ *
+ * You can optionally provide metadata about your app to SDL. This is not
+ * required, but strongly encouraged.
+ *
+ * There are several locations where SDL can make use of metadata (an "About"
+ * box in the macOS menu bar, the name of the app can be shown on some audio
+ * mixers, etc). Any piece of metadata can be left out, if a specific detail
+ * doesn't make sense for the app.
+ *
+ * This function should be called as early as possible, before SDL_Init.
+ * Multiple calls to this function are allowed, but various state might not
+ * change once it has been set up with a previous call to this function.
+ *
+ * Once set, this metadata can be read using SDL_GetMetadataProperty().
+ *
+ * These are the supported properties:
+ *
+ * - `SDL_PROP_APP_METADATA_NAME_STRING`: The human-readable name of
+ *   the application, like "My Game 2: Bad Guy's Revenge!". This defaults to "SDL Application".
+ * - SDL_PROP_APP_METADATA_VERSION_STRING`: The version of the app that
+ *   is running; there are no rules on format, so "1.0.3beta2" and
+ *   "April 22nd, 2024" and a git hash are all valid options. This has no default.
+ * - `SDL_PROP_APP_METADATA_IDENTIFIER_STRING`: A unique string that
+ *   identifies this app. This must be in reverse-domain format, like
+ *   "com.example.mygame2". This string is used by desktop compositors to
+ *   identify and group windows together, as well as match applications
+ *   with associated desktop settings and icons. This has no default.
+ * - SDL_PROP_APP_METADATA_CREATOR_STRING`: The human-readable name
+ *   of the creator/developer/maker of this app, like "MojoWorkshop, LLC"
+ * - SDL_PROP_APP_METADATA_COPYRIGHT_STRING`: The human-readable copyright
+ *   notice, like "Copyright (c) 2024 MojoWorkshop, LLC" or whatnot. Keep
+ *   this to one line, don't paste a copy of a whole software license in
+ *   here. This has no default.
+ * - SDL_PROP_APP_METADATA_URL_STRING`: A URL to the app on the web. Maybe
+ *   a product page, or a storefront, or even a GitHub repository, for
+ *   user's further information This has no default.
+ * - SDL_PROP_APP_METADATA_TYPE_STRING`: The type of application this is.
+ *   Currently this string can be "game" for a video game, "mediaplayer" for
+ *   a media player, or generically "application" if nothing else applies.
+ *   Future versions of SDL might add new types. This defaults to "application".
+ *
+ * \param name the name of the metadata property to set.
+ * \param value the value of the property, or NULL to remove that property.
+ * \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.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \sa SDL_GetAppMetadataProperty
+ * \sa SDL_SetAppMetadata
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAppMetadataProperty(const char *name, const char *value);
+
+#define SDL_PROP_APP_METADATA_NAME_STRING         "SDL.app.metadata.name"
+#define SDL_PROP_APP_METADATA_VERSION_STRING      "SDL.app.metadata.version"
+#define SDL_PROP_APP_METADATA_IDENTIFIER_STRING   "SDL.app.metadata.identifier"
+#define SDL_PROP_APP_METADATA_CREATOR_STRING      "SDL.app.metadata.creator"
+#define SDL_PROP_APP_METADATA_COPYRIGHT_STRING    "SDL.app.metadata.copyright"
+#define SDL_PROP_APP_METADATA_URL_STRING          "SDL.app.metadata.url"
+#define SDL_PROP_APP_METADATA_TYPE_STRING         "SDL.app.metadata.type"
+
+/**
+ * Get metadata about your app.
+ *
+ * This returns metadata previously set using SDL_SetAppMetadata() or SDL_SetAppMetadataProperty(). See SDL_SetAppMetadataProperty() for the list of available properties and their meanings.
+ *
+ * \param name the name of the metadata property to get.
+ * \returns the current value of the metadata property, or the default if it is not set, NULL for properties with no default.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \threadsafety It is safe to call this function from any thread, although
+ *               the string returned is not protected and could potentially be
+ *               freed if you call SDL_SetAppMetadataProperty() to set that property from another thread.
+ *
+ * \sa SDL_SetAppMetadata
+ * \sa SDL_SetAppMetadataProperty
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_GetAppMetadataProperty(const char *name);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }

+ 57 - 0
src/SDL.c

@@ -107,6 +107,63 @@ SDL_NORETURN void SDL_ExitProcess(int exitcode)
 #endif
 }
 
+/* App metadata */
+
+int SDL_SetAppMetadata(const char *appname, const char *appversion, const char *appidentifier)
+{
+    SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING, appname);
+    SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING, appversion);
+    SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, appidentifier);
+    return 0;
+}
+
+static SDL_bool SDL_ValidMetadataProperty(const char *name)
+{
+    if (!name || !*name) {
+        return SDL_FALSE;
+    }
+
+    if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0 ||
+        SDL_strcmp(name, SDL_PROP_APP_METADATA_VERSION_STRING) == 0 ||
+        SDL_strcmp(name, SDL_PROP_APP_METADATA_IDENTIFIER_STRING) == 0 ||
+        SDL_strcmp(name, SDL_PROP_APP_METADATA_CREATOR_STRING) == 0 ||
+        SDL_strcmp(name, SDL_PROP_APP_METADATA_COPYRIGHT_STRING) == 0 ||
+        SDL_strcmp(name, SDL_PROP_APP_METADATA_URL_STRING) == 0 ||
+        SDL_strcmp(name, SDL_PROP_APP_METADATA_TYPE_STRING) == 0) {
+        return SDL_TRUE;
+    }
+    return SDL_FALSE;
+}
+
+int SDL_SetAppMetadataProperty(const char *name, const char *value)
+{
+    if (!SDL_ValidMetadataProperty(name)) {
+        return SDL_InvalidParamError("name");
+    }
+
+    return SDL_SetStringProperty(SDL_GetGlobalProperties(), name, value);
+}
+
+const char *SDL_GetAppMetadataProperty(const char *name)
+{
+    if (!SDL_ValidMetadataProperty(name)) {
+        SDL_InvalidParamError("name");
+        return NULL;
+    }
+
+    const SDL_PropertiesID props = SDL_GetGlobalProperties();
+    const char *value = SDL_GetStringProperty(props, name, NULL);
+    if (!value || !*value) {
+        if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0) {
+            value = "SDL Application";
+        } else if (SDL_strcmp(name, SDL_PROP_APP_METADATA_TYPE_STRING) == 0) {
+            value = "application";
+        }
+    }
+    return value;
+}
+
+
 /* The initialized subsystems */
 #ifdef SDL_MAIN_NEEDED
 static SDL_bool SDL_MainIsReady = SDL_FALSE;

+ 2 - 10
src/audio/jack/SDL_jackaudio.c

@@ -261,18 +261,10 @@ static void JACK_CloseDevice(SDL_AudioDevice *device)
     }
 }
 
-// !!! FIXME: unify this (PulseAudio has a getAppName, Pipewire has a thing, etc
+// !!! FIXME: unify this (PulseAudio has a getAppName, Pipewire has a thing, etc)
 static const char *GetJackAppName(void)
 {
-    const char *retval = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME);
-    if (retval && *retval) {
-        return retval;
-    }
-    retval = SDL_GetHint(SDL_HINT_APP_NAME);
-    if (retval && *retval) {
-        return retval;
-    }
-    return "SDL Application";
+    return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
 }
 
 static int JACK_OpenDevice(SDL_AudioDevice *device)

+ 2 - 8
src/audio/pipewire/SDL_pipewire.c

@@ -1049,13 +1049,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device)
     const int min_period = PW_MIN_SAMPLES * SPA_MAX(device->spec.freq / PW_BASE_CLOCK_RATE, 1);
 
     // Get the hints for the application name, icon name, stream name and role
-    app_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME);
-    if (!app_name || *app_name == '\0') {
-        app_name = SDL_GetHint(SDL_HINT_APP_NAME);
-        if (!app_name || *app_name == '\0') {
-            app_name = "SDL Application";
-        }
-    }
+    app_name = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
 
     icon_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME);
     if (!icon_name || *icon_name == '\0') {
@@ -1063,7 +1057,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device)
     }
 
     // App ID. Default to NULL if not available.
-    app_id = SDL_GetHint(SDL_HINT_APP_ID);
+    app_id = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING);
 
     stream_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME);
     if (!stream_name || *stream_name == '\0') {

+ 1 - 26
src/audio/pulseaudio/SDL_pulseaudio.c

@@ -264,34 +264,9 @@ static int load_pulseaudio_syms(void)
     return 0;
 }
 
-static SDL_INLINE int squashVersion(const int major, const int minor, const int patch)
-{
-    return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF);
-}
-
-// Workaround for older pulse: pa_context_new() must have non-NULL appname
 static const char *getAppName(void)
 {
-    const char *retval = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME);
-    if (retval && *retval) {
-        return retval;
-    }
-    retval = SDL_GetHint(SDL_HINT_APP_NAME);
-    if (retval && *retval) {
-        return retval;
-    } else {
-        const char *verstr = PULSEAUDIO_pa_get_library_version();
-        retval = "SDL Application"; // the "oh well" default.
-        if (verstr) {
-            int maj, min, patch;
-            if (SDL_sscanf(verstr, "%d.%d.%d", &maj, &min, &patch) == 3) {
-                if (squashVersion(maj, min, patch) >= squashVersion(0, 9, 15)) {
-                    retval = NULL; // 0.9.15+ handles NULL correctly.
-                }
-            }
-        }
-    }
-    return retval;
+    return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
 }
 
 static void OperationStateChangeCallback(pa_operation *o, void *userdata)

+ 1 - 4
src/core/linux/SDL_dbus.c

@@ -495,11 +495,8 @@ SDL_bool SDL_DBus_ScreensaverInhibit(SDL_bool inhibit)
         const char *interface = "org.freedesktop.ScreenSaver";
 
         if (inhibit) {
-            const char *app = SDL_GetHint(SDL_HINT_APP_NAME);
+            const char *app = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
             const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);
-            if (!app || !app[0]) {
-                app = "My SDL application";
-            }
             if (!reason || !reason[0]) {
                 reason = default_inhibit_reason;
             }

+ 1 - 2
src/core/unix/SDL_appid.c

@@ -59,8 +59,7 @@ const char *SDL_GetExeName(void)
 
 const char *SDL_GetAppID(void)
 {
-    /* Always check the hint, as it may have changed */
-    const char *id_str = SDL_GetHint(SDL_HINT_APP_ID);
+    const char *id_str = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
 
     if (!id_str) {
         /* If the hint isn't set, try to use the application's executable name */

+ 3 - 0
src/dynapi/SDL_dynapi.sym

@@ -158,6 +158,7 @@ SDL3_0.0.0 {
     SDL_GetAndroidInternalStoragePath;
     SDL_GetAndroidJNIEnv;
     SDL_GetAndroidSDKVersion;
+    SDL_GetAppMetadataProperty;
     SDL_GetAssertionHandler;
     SDL_GetAssertionReport;
     SDL_GetAudioDeviceChannelMap;
@@ -694,6 +695,8 @@ SDL3_0.0.0 {
     SDL_SendGamepadEffect;
     SDL_SendJoystickEffect;
     SDL_SendJoystickVirtualSensorData;
+    SDL_SetAppMetadata;
+    SDL_SetAppMetadataProperty;
     SDL_SetAssertionHandler;
     SDL_SetAudioDeviceGain;
     SDL_SetAudioPostmixCallback;

+ 3 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -183,6 +183,7 @@
 #define SDL_GetAndroidInternalStoragePath SDL_GetAndroidInternalStoragePath_REAL
 #define SDL_GetAndroidJNIEnv SDL_GetAndroidJNIEnv_REAL
 #define SDL_GetAndroidSDKVersion SDL_GetAndroidSDKVersion_REAL
+#define SDL_GetAppMetadataProperty SDL_GetAppMetadataProperty_REAL
 #define SDL_GetAssertionHandler SDL_GetAssertionHandler_REAL
 #define SDL_GetAssertionReport SDL_GetAssertionReport_REAL
 #define SDL_GetAudioDeviceChannelMap SDL_GetAudioDeviceChannelMap_REAL
@@ -719,6 +720,8 @@
 #define SDL_SendGamepadEffect SDL_SendGamepadEffect_REAL
 #define SDL_SendJoystickEffect SDL_SendJoystickEffect_REAL
 #define SDL_SendJoystickVirtualSensorData SDL_SendJoystickVirtualSensorData_REAL
+#define SDL_SetAppMetadata SDL_SetAppMetadata_REAL
+#define SDL_SetAppMetadataProperty SDL_SetAppMetadataProperty_REAL
 #define SDL_SetAssertionHandler SDL_SetAssertionHandler_REAL
 #define SDL_SetAudioDeviceGain SDL_SetAudioDeviceGain_REAL
 #define SDL_SetAudioPostmixCallback SDL_SetAudioPostmixCallback_REAL

+ 3 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -203,6 +203,7 @@ SDL_DYNAPI_PROC(Uint32,SDL_GetAndroidExternalStorageState,(void),(),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetAndroidInternalStoragePath,(void),(),return)
 SDL_DYNAPI_PROC(void*,SDL_GetAndroidJNIEnv,(void),(),return)
 SDL_DYNAPI_PROC(int,SDL_GetAndroidSDKVersion,(void),(),return)
+SDL_DYNAPI_PROC(const char*,SDL_GetAppMetadataProperty,(const char *a),(a),return)
 SDL_DYNAPI_PROC(SDL_AssertionHandler,SDL_GetAssertionHandler,(void **a),(a),return)
 SDL_DYNAPI_PROC(const SDL_AssertData*,SDL_GetAssertionReport,(void),(),return)
 SDL_DYNAPI_PROC(int*,SDL_GetAudioDeviceChannelMap,(SDL_AudioDeviceID a, int *b),(a,b),return)
@@ -730,6 +731,8 @@ SDL_DYNAPI_PROC(int,SDL_SendAndroidMessage,(Uint32 a, int b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SendGamepadEffect,(SDL_Gamepad *a, const void *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SendJoystickEffect,(SDL_Joystick *a, const void *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SendJoystickVirtualSensorData,(SDL_Joystick *a, SDL_SensorType b, Uint64 c, const float *d, int e),(a,b,c,d,e),return)
+SDL_DYNAPI_PROC(int,SDL_SetAppMetadata,(const char *a, const char *b, const char *c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_SetAppMetadataProperty,(const char *a, const char *b),(a,b),return)
 SDL_DYNAPI_PROC(void,SDL_SetAssertionHandler,(SDL_AssertionHandler a, void *b),(a,b),)
 SDL_DYNAPI_PROC(int,SDL_SetAudioDeviceGain,(SDL_AudioDeviceID a, float b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetAudioPostmixCallback,(SDL_AudioDeviceID a, SDL_AudioPostmixCallback b, void *c),(a,b,c),return)

+ 14 - 3
src/video/cocoa/SDL_cocoaevents.m

@@ -362,12 +362,19 @@ static SDL3AppDelegate *appDelegate = nil;
 
 static NSString *GetApplicationName(void)
 {
-    NSString *appName;
+    NSString *appName = nil;
+
+    const char *metaname = SDL_GetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_APP_METADATA_NAME_STRING, NULL);
+    if (metaname && *metaname) {
+        appName = [NSString stringWithUTF8String:metaname];
+    }
 
     /* Determine the application name */
-    appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
     if (!appName) {
-        appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
+        appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
+        if (!appName) {
+            appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
+        }
     }
 
     if (![appName length]) {
@@ -420,6 +427,10 @@ static void CreateApplicationMenus(void)
 
     /* Add menu items */
     title = [@"About " stringByAppendingString:appName];
+
+    // !!! FIXME: Menu items can't take parameters, just a basic selector, so this should instead call a selector
+    // !!! FIXME: that itself calls -[NSApplication orderFrontStandardAboutPanelWithOptions:optionsDictionary],
+    // !!! FIXME: filling in that NSDictionary with SDL_GetAppMetadataProperty()
     [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
 
     [appleMenu addItem:[NSMenuItem separatorItem]];