summaryrefslogtreecommitdiffstats
path: root/src/detection/sound/sound_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/sound/sound_linux.c')
-rw-r--r--src/detection/sound/sound_linux.c164
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
+}