diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/sound/sound_linux.c | |
Add the files
Diffstat (limited to 'src/detection/sound/sound_linux.c')
| -rw-r--r-- | src/detection/sound/sound_linux.c | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/src/detection/sound/sound_linux.c b/src/detection/sound/sound_linux.c new file mode 100644 index 0000000..8af7e93 --- /dev/null +++ b/src/detection/sound/sound_linux.c @@ -0,0 +1,164 @@ +#include "sound.h" + +#ifdef FF_HAVE_PULSE + #include "common/library.h" + #include <pulse/pulseaudio.h> + +struct DetectionInfoBundle { + FFstrbuf serverName; + FFstrbuf defaultDeviceId; + FFlist* result; + FFSoundOptions* options; +}; + +static void paSinkInfoCallback(FF_A_UNUSED pa_context* c, const pa_sink_info* i, int eol, void* userdata) { + if (eol > 0 || !i) { + return; + } + + struct DetectionInfoBundle* bundle = userdata; + + bool isMain = ffStrbufEqualS(&bundle->defaultDeviceId, i->name); + if ((bundle->options->soundType & FF_SOUND_TYPE_MAIN) && !isMain) { + return; + } + + bool isActive = i->active_port && i->active_port->available != PA_PORT_AVAILABLE_NO; + if ((bundle->options->soundType & FF_SOUND_TYPE_ACTIVE) && !isActive) { + return; + } + + FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *bundle->result); + ffStrbufInitS(&device->identifier, i->name); + ffStrbufInitCopy(&device->platformApi, &bundle->serverName); + ffStrbufTrimRightSpace(&device->identifier); + ffStrbufInitS(&device->name, i->description); + ffStrbufTrimRightSpace(&device->name); + ffStrbufTrimLeft(&device->name, ' '); + device->volume = i->mute ? 0 : (uint8_t) ((i->volume.values[0] * 100 + PA_VOLUME_NORM / 2 /*round*/) / PA_VOLUME_NORM); + device->type = (isMain ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE) | (isActive ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE); +} + +static void paServerInfoCallback(FF_A_UNUSED pa_context* c, const pa_server_info* i, void* userdata) { + if (!i) { + return; + } + + struct DetectionInfoBundle* bundle = userdata; + + const char* realServer = strstr(i->server_name, "(on "); + if (realServer) { + ffStrbufSetS(&bundle->serverName, realServer + strlen("(on ")); + ffStrbufTrimRight(&bundle->serverName, ')'); + } else { + ffStrbufSetF(&bundle->serverName, "%s %s", i->server_name, i->server_version); + } + + ffStrbufSetS(&bundle->defaultDeviceId, i->default_sink_name); +} + +static const char* detectSound(FFSoundOptions* options, FFlist* devices) { + FF_LIBRARY_LOAD_MESSAGE(pulse, "libpulse" FF_LIBRARY_EXTENSION, 0) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_new) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_get_api) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_iterate) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_free) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_new) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_connect) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_get_state) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_get_sink_info_list) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_get_server_info) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_unref) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_operation_cancel) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_operation_get_state) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_operation_unref) + + pa_mainloop* mainloop = ffpa_mainloop_new(); + if (!mainloop) { + return "Failed to create pulseaudio mainloop"; + } + + pa_mainloop_api* mainloopApi = ffpa_mainloop_get_api(mainloop); + if (!mainloopApi) { + ffpa_mainloop_free(mainloop); + return "Failed to get pulseaudio mainloop api"; + } + + pa_context* context = ffpa_context_new(mainloopApi, "fastfetch"); + if (!context) { + ffpa_mainloop_free(mainloop); + return "Failed to create pulseaudio context"; + } + + struct DetectionInfoBundle bundle = { + .serverName = ffStrbufCreate(), + .defaultDeviceId = ffStrbufCreate(), + .result = devices, + .options = options, + }; + const char* error = NULL; + + if (ffpa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + error = "Failed to connect to pulseaudio context"; + goto exit; + } + + pa_context_state_t state; + while ((state = ffpa_context_get_state(context)) != PA_CONTEXT_READY) { + if (!PA_CONTEXT_IS_GOOD(state)) { + error = "Failed to get pulseaudio context state"; + goto exit; + } + + ffpa_mainloop_iterate(mainloop, 1, NULL); + } + + { + pa_operation* operation = ffpa_context_get_server_info(context, paServerInfoCallback, &bundle); + if (!operation) { + error = "Failed to get pulseaudio server info"; + goto exit; + } + while (ffpa_operation_get_state(operation) == PA_OPERATION_RUNNING) { + ffpa_mainloop_iterate(mainloop, 1, NULL); + } + + ffpa_operation_unref(operation); + } + + { + pa_operation* operation = ffpa_context_get_sink_info_list(context, paSinkInfoCallback, &bundle); + if (!operation) { + error = "Failed to get pulseaudio sink info list"; + goto exit; + } + + while (ffpa_operation_get_state(operation) == PA_OPERATION_RUNNING) { + if (options->soundType & FF_SOUND_TYPE_MAIN && devices->length > 0) { + ffpa_operation_cancel(operation); + } + + ffpa_mainloop_iterate(mainloop, 1, NULL); + } + + ffpa_operation_unref(operation); + } + +exit: + ffStrbufDestroy(&bundle.serverName); + ffStrbufDestroy(&bundle.defaultDeviceId); + ffpa_context_unref(context); + ffpa_mainloop_free(mainloop); + return error; +} + +#endif // FF_HAVE_PULSE + +const char* ffDetectSound(FFSoundOptions* options, FFlist* devices) { +#ifdef FF_HAVE_PULSE + return detectSound(options, devices); +#else + FF_UNUSED(options, devices); + return "Fastfetch was built without libpulse support"; +#endif +} |