mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-11-08 06:00:08 +01:00
165 lines
5.5 KiB
C
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
|
|
}
|