Files
fastfetch/src/detection/sound/sound_linux.c
T
2026-04-15 16:17:08 +08:00

165 lines
5.5 KiB
C

#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
}