Browse Source

Add SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME

Thomas J Faughnan Jr 11 months ago
parent
commit
ad166be1c5

+ 22 - 0
include/SDL3/SDL_hints.h

@@ -292,6 +292,28 @@ extern "C" {
  */
 #define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME"
 
+/**
+ * Specify an application icon name for an audio device.
+ *
+ * Some audio backends (such as Pulseaudio and Pipewire) allow you to set an
+ * XDG icon name for your application. Among other things, this icon 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. Note that this
+ * is unrelated to the icon used by the windowing system, which may be set with
+ * SDL_SetWindowIcon (or via desktop file on Wayland).
+ *
+ * Setting this to "" or leaving it unset will have SDL use a reasonable
+ * default, "applications-games", which is likely to be installed.
+ * See https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
+ * and https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
+ * for the relevant XDG icon specs.
+ *
+ * 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_ICON_NAME "SDL_AUDIO_DEVICE_APP_ICON_NAME"
+
 /**
  * A variable controlling device buffer size.
  *

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

@@ -1108,7 +1108,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device)
     const struct spa_pod *params = NULL;
     struct SDL_PrivateAudioData *priv;
     struct pw_properties *props;
-    const char *app_name, *app_id, *stream_name, *stream_role, *error;
+    const char *app_name, *icon_name, *app_id, *stream_name, *stream_role, *error;
     Uint32 node_id = !device->handle ? PW_ID_ANY : PW_HANDLE_TO_ID(device->handle);
     const SDL_bool iscapture = device->iscapture;
     int res;
@@ -1116,7 +1116,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device)
     // Clamp the period size to sane values
     const int min_period = PW_MIN_SAMPLES * SPA_MAX(device->spec.freq / PW_BASE_CLOCK_RATE, 1);
 
-    // Get the hints for the application name, stream name and role
+    // 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);
@@ -1125,6 +1125,11 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device)
         }
     }
 
+    icon_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME);
+    if (!icon_name || *icon_name == '\0') {
+        icon_name = "applications-games";
+    }
+
     // App ID. Default to NULL if not available.
     app_id = SDL_GetHint(SDL_HINT_APP_ID);
 
@@ -1190,6 +1195,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device)
     PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, iscapture ? "Capture" : "Playback");
     PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_ROLE, stream_role);
     PIPEWIRE_pw_properties_set(props, PW_KEY_APP_NAME, app_name);
+    PIPEWIRE_pw_properties_set(props, PW_KEY_APP_ICON_NAME, icon_name);
     if (app_id) {
         PIPEWIRE_pw_properties_set(props, PW_KEY_APP_ID, app_id);
     }

+ 25 - 5
src/audio/pulseaudio/SDL_pulseaudio.c

@@ -66,6 +66,9 @@ static const char *(*PULSEAUDIO_pa_get_library_version)(void);
 static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto)(
     pa_channel_map *, unsigned, pa_channel_map_def_t);
 static const char *(*PULSEAUDIO_pa_strerror)(int);
+static pa_proplist *(*PULSEAUDIO_pa_proplist_new)(void);
+static void (*PULSEAUDIO_pa_proplist_free)(pa_proplist *);
+static int (*PULSEAUDIO_pa_proplist_sets)(pa_proplist *, const char *, const char *);
 
 static pa_threaded_mainloop *(*PULSEAUDIO_pa_threaded_mainloop_new)(void);
 static void (*PULSEAUDIO_pa_threaded_mainloop_set_name)(pa_threaded_mainloop *, const char *);
@@ -84,8 +87,9 @@ static void (*PULSEAUDIO_pa_operation_set_state_callback)(pa_operation *, pa_ope
 static void (*PULSEAUDIO_pa_operation_cancel)(pa_operation *);
 static void (*PULSEAUDIO_pa_operation_unref)(pa_operation *);
 
-static pa_context *(*PULSEAUDIO_pa_context_new)(pa_mainloop_api *,
-                                                const char *);
+static pa_context *(*PULSEAUDIO_pa_context_new_with_proplist)(pa_mainloop_api *,
+                                                const char *,
+                                                const pa_proplist *);
 static void (*PULSEAUDIO_pa_context_set_state_callback)(pa_context *, pa_context_notify_cb_t, void *);
 static int (*PULSEAUDIO_pa_context_connect)(pa_context *, const char *,
                                             pa_context_flags_t, const pa_spawn_api *);
@@ -205,7 +209,7 @@ static int load_pulseaudio_syms(void)
     SDL_PULSEAUDIO_SYM(pa_operation_get_state);
     SDL_PULSEAUDIO_SYM(pa_operation_cancel);
     SDL_PULSEAUDIO_SYM(pa_operation_unref);
-    SDL_PULSEAUDIO_SYM(pa_context_new);
+    SDL_PULSEAUDIO_SYM(pa_context_new_with_proplist);
     SDL_PULSEAUDIO_SYM(pa_context_set_state_callback);
     SDL_PULSEAUDIO_SYM(pa_context_connect);
     SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list);
@@ -238,6 +242,9 @@ static int load_pulseaudio_syms(void)
     SDL_PULSEAUDIO_SYM(pa_stream_set_write_callback);
     SDL_PULSEAUDIO_SYM(pa_stream_set_read_callback);
     SDL_PULSEAUDIO_SYM(pa_context_get_server_info);
+    SDL_PULSEAUDIO_SYM(pa_proplist_new);
+    SDL_PULSEAUDIO_SYM(pa_proplist_free);
+    SDL_PULSEAUDIO_SYM(pa_proplist_sets);
 
     // optional
 #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
@@ -337,6 +344,8 @@ static void PulseContextStateChangeCallback(pa_context *context, void *userdata)
 static int ConnectToPulseServer(void)
 {
     pa_mainloop_api *mainloop_api = NULL;
+    pa_proplist *proplist = NULL;
+    const char *icon_name;
     int state = 0;
 
     SDL_assert(pulseaudio_threaded_mainloop == NULL);
@@ -362,11 +371,22 @@ static int ConnectToPulseServer(void)
     mainloop_api = PULSEAUDIO_pa_threaded_mainloop_get_api(pulseaudio_threaded_mainloop);
     SDL_assert(mainloop_api != NULL); // this never fails, right?
 
-    pulseaudio_context = PULSEAUDIO_pa_context_new(mainloop_api, getAppName());
+    if (!(proplist = PULSEAUDIO_pa_proplist_new())) {
+        return SDL_SetError("pa_proplist_new() failed");
+    }
+
+    icon_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME);
+    if (!icon_name || *icon_name == '\0') {
+        icon_name = "applications-games";
+    }
+    PULSEAUDIO_pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, icon_name);
+
+    pulseaudio_context = PULSEAUDIO_pa_context_new_with_proplist(mainloop_api, getAppName(), proplist);
     if (!pulseaudio_context) {
-        SDL_SetError("pa_context_new() failed");
+        SDL_SetError("pa_context_new_with_proplist() failed");
         goto failed;
     }
+    PULSEAUDIO_pa_proplist_free(proplist);
 
     PULSEAUDIO_pa_context_set_state_callback(pulseaudio_context, PulseContextStateChangeCallback, NULL);