From 60f26182c3a44544f78c1569906fdf58d1d69374 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 1 Apr 2024 09:07:02 -0400 Subject: [PATCH] audio: Prefer Pipewire over Pulseaudio if the pipewire-pulse service is running Use DBus to query Systemd to check if the pipewire-pulse service is in the "running" state. If it is, then it is certain that Pipewire is being used instead of Pulseaudio as the preferred system mixer. If DBus support is not enabled or Systemd is not being used on the underlying system, this check will simply fail and the standard driver order will be tested. --- src/audio/SDL_audio.c | 35 ++++++++++++++-- src/audio/SDL_sysaudio.h | 1 + src/audio/pipewire/SDL_pipewire.c | 34 ++++++++++++++- src/core/linux/SDL_dbus.c | 69 +++++++++++++++++++++++++++++++ src/core/linux/SDL_dbus.h | 2 + 5 files changed, 136 insertions(+), 5 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 5ca394dbec14a..99042d7aef4dc 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 eb6b38bad83ef..0687e8ce37280 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 9941f17d1a435..a699746af001b 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 9e6a56b65e1d8..a0c96a878b71e 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -632,4 +632,73 @@ char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count) 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 0be866b36059d..d6841a6db14a3 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_ */