diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 5ca394dbe..99042d7ae 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -28,6 +28,9 @@ // Available audio drivers static const AudioBootStrap *const bootstrap[] = { #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO +#ifdef SDL_AUDIO_DRIVER_PIPEWIRE + &PIPEWIRE_PREFERRED_bootstrap, +#endif &PULSEAUDIO_bootstrap, #endif #ifdef SDL_AUDIO_DRIVER_PIPEWIRE @@ -98,15 +101,41 @@ static const AudioBootStrap *const bootstrap[] = { static SDL_AudioDriver current_audio; +// Deduplicated list of audio bootstrap drivers. +static const AudioBootStrap *deduped_bootstrap[SDL_arraysize(bootstrap) - 1]; + int SDL_GetNumAudioDrivers(void) { - return SDL_arraysize(bootstrap) - 1; + static int num_drivers = -1; + + if (num_drivers >= 0) { + return num_drivers; + } + + num_drivers = 0; + + // Build a list of unique audio drivers. + for (int i = 0; bootstrap[i] != NULL; ++i) { + SDL_bool duplicate = SDL_FALSE; + for (int j = 0; j < i; ++j) { + if (SDL_strcmp(bootstrap[i]->name, bootstrap[j]->name) == 0) { + duplicate = SDL_TRUE; + break; + } + } + + if (!duplicate) { + deduped_bootstrap[num_drivers++] = bootstrap[i]; + } + } + + return num_drivers; } const char *SDL_GetAudioDriver(int index) { if (index >= 0 && index < SDL_GetNumAudioDrivers()) { - return bootstrap[index]->name; + return deduped_bootstrap[index]->name; } return NULL; } @@ -887,8 +916,8 @@ int SDL_InitAudio(const char *driver_name) current_audio.name = bootstrap[i]->name; current_audio.desc = bootstrap[i]->desc; initialized = SDL_TRUE; + break; } - break; } } diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index eb6b38bad..0687e8ce3 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -335,6 +335,7 @@ typedef struct AudioBootStrap } AudioBootStrap; // Not all of these are available in a given build. Use #ifdefs, etc. +extern AudioBootStrap PIPEWIRE_PREFERRED_bootstrap; extern AudioBootStrap PIPEWIRE_bootstrap; extern AudioBootStrap PULSEAUDIO_bootstrap; extern AudioBootStrap ALSA_bootstrap; diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index 9941f17d1..a699746af 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -29,6 +29,17 @@ #include #include +#include "../../core/linux/SDL_dbus.h" + +static SDL_bool CheckPipewirePulseService() +{ +#ifdef SDL_USE_LIBDBUS + return SDL_DBus_QuerySystemdUnitRunning("pipewire-pulse.service", SDL_TRUE); +#else + return SDL_FALSE; +#endif +} + /* * The following keys are defined for compatibility when building against older versions of Pipewire * prior to their introduction and can be removed if the minimum required Pipewire build version is @@ -1251,7 +1262,7 @@ static void PIPEWIRE_Deinitialize(void) } } -static SDL_bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) +static SDL_bool PipewireInitialize(SDL_AudioDriverImpl *impl) { if (!pipewire_initialized) { if (init_pipewire_library() < 0) { @@ -1282,6 +1293,25 @@ static SDL_bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) return SDL_TRUE; } -AudioBootStrap PIPEWIRE_bootstrap = { "pipewire", "Pipewire", PIPEWIRE_Init, SDL_FALSE }; +static SDL_bool PIPEWIRE_PREFERRED_Init(SDL_AudioDriverImpl *impl) +{ + if (CheckPipewirePulseService()) { + return PipewireInitialize(impl); + } + + return SDL_FALSE; +} + +static SDL_bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) +{ + return PipewireInitialize(impl); +} + +AudioBootStrap PIPEWIRE_PREFERRED_bootstrap = { + "pipewire", "Pipewire", PIPEWIRE_PREFERRED_Init, SDL_FALSE +}; +AudioBootStrap PIPEWIRE_bootstrap = { + "pipewire", "Pipewire", PIPEWIRE_Init, SDL_FALSE +}; #endif // SDL_AUDIO_DRIVER_PIPEWIRE diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 9e6a56b65..a0c96a878 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -632,4 +632,73 @@ failed: return NULL; } +/* Check to see if a Systemd unit exists and is currently running. */ +SDL_bool SDL_DBus_QuerySystemdUnitRunning(const char *unit_name, SDL_bool user_unit) +{ + const char *path, *prop; + DBusError err; + SDL_bool running = SDL_FALSE; + + /* Make sure we have a connection to the dbus session bus */ + if (!SDL_DBus_GetContext() || !dbus.session_conn) { + /* We either cannot connect to the session bus or were unable to + * load the D-Bus library at all. */ + return SDL_FALSE; + } + + /* Make sure the appropriate bus is available. */ + if ((user_unit && !dbus.session_conn) || (!user_unit && !dbus.system_conn)) { + return SDL_FALSE; + } + + dbus.error_init(&err); + + /* Get the object path for the unit. */ + DBusMessage *method = dbus.message_new_method_call("org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit"); + if (!method) { + return SDL_FALSE; + } + + if (!dbus.message_append_args(method, DBUS_TYPE_STRING, &unit_name, DBUS_TYPE_INVALID)) { + SDL_OutOfMemory(); + dbus.message_unref(method); + return SDL_FALSE; + } + + DBusMessage *reply = dbus.connection_send_with_reply_and_block(user_unit ? dbus.session_conn : dbus.system_conn, method, DBUS_TIMEOUT_USE_DEFAULT, &err); + dbus.message_unref(method); + if (!reply) { + if (dbus.error_is_set(&err)) { + SDL_SetError("%s: %s", err.name, err.message); + dbus.error_free(&err); + } + return SDL_FALSE; + } + + DBusMessageIter reply_iter; + if (!dbus.message_iter_init(reply, &reply_iter)) { + goto done; + } + + if (dbus.message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_OBJECT_PATH) { + goto done; + } + + dbus.message_iter_get_basic(&reply_iter, &path); + + /* We want to know the substate of the unit, which should be the string "running". */ + if (SDL_DBus_QueryPropertyOnConnection(user_unit ? dbus.session_conn : dbus.system_conn, + "org.freedesktop.systemd1", path, "org.freedesktop.systemd1.Unit", + "SubState", DBUS_TYPE_STRING, &prop)) { + running = SDL_strcmp(prop, "running") == 0; + } + +done: + dbus.message_unref(reply); + return running; +} + #endif diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 0be866b36..d6841a6db 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -109,6 +109,8 @@ extern char *SDL_DBus_GetLocalMachineId(void); extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_count); +extern SDL_bool SDL_DBus_QuerySystemdUnitRunning(const char *unit_name, SDL_bool user_unit); + #endif /* HAVE_DBUS_DBUS_H */ #endif /* SDL_dbus_h_ */