Sound(linux): Implement using libpulse

Also differentiate between active and main device(s)
This commit is contained in:
Linus Dierheimer
2023-01-26 15:48:28 +01:00
parent 856c9d58b8
commit 184ada6ae8
13 changed files with 156 additions and 101 deletions
+5 -5
View File
@@ -70,7 +70,7 @@ cmake_dependent_option(ENABLE_OPENCL "Enable opencl" ON "LINUX OR BSD OR WIN32"
cmake_dependent_option(ENABLE_LIBCJSON "Enable libcjson" ON "LINUX OR WIN32" OFF)
cmake_dependent_option(ENABLE_LIBNM "Enable libnm" ON "LINUX" OFF)
cmake_dependent_option(ENABLE_FREETYPE "Enable freetype" ON "ANDROID" OFF)
cmake_dependent_option(ENABLE_ALSA "Enable alsa-lib" ON "LINUX" OFF)
cmake_dependent_option(ENABLE_PULSE "Enable pulse" ON "LINUX OR BSD" OFF)
cmake_dependent_option(ENABLE_THREADS "Enable multithreading" ON "Threads_FOUND AND NOT ANDROID" OFF)
cmake_dependent_option(ENABLE_BUFFER "Enable stdout buffer" ON "LINUX OR APPLE OR BSD OR WIN32 OR ANDROID" OFF)
cmake_dependent_option(USE_WIN_NTAPI "Allow using internal NTAPI" ON "WIN32" OFF)
@@ -425,7 +425,7 @@ elseif(BSD)
src/detection/poweradapter/poweradapter_nosupport.c
src/detection/processes/processes_bsd.c
src/detection/qt.c
src/detection/sound/sound_nosupport.c
src/detection/sound/sound_linux.c
src/detection/swap/swap_bsd.c
src/detection/temps/temps_linux.c
src/detection/terminalfont/terminalfont_linux.c
@@ -663,9 +663,9 @@ ff_lib_enable(FREETYPE
"freetype2"
"FreeType2"
)
ff_lib_enable(ALSA
"alsa"
"ALSA"
ff_lib_enable(PULSE
"libpulse"
"Pulse"
)
if(ENABLE_THREADS)
+1 -1
View File
@@ -55,7 +55,7 @@ The following libraries are used if present at runtime:
* [`librpm`](http://rpm.org/): Slower fallback for rpm package count. Needed on openSUSE.
* [`libcJSON`](https://github.com/DaveGamble/cJSON): Needed for Windows Terminal font ( WSL ).
* [`libnm`](https://networkmanager.dev/docs/libnm/latest/): Used for Wifi detection.
* [`libalsa`](https://www.alsa-project.org/alsa-doc/alsa-lib/): Used for Sound detection.
* [`libpulse`](https://freedesktop.org/software/pulseaudio/doxygen/): Used for Sound detection.
### macOS
+1 -1
View File
@@ -379,7 +379,7 @@ __fastfetch_completion()
"--lib-glx"
"--lib-osmesa"
"--lib-opencl"
"--lib-alsa"
"--lib-pulse"
"--battery-dir"
)
+4 -4
View File
@@ -140,7 +140,7 @@ static void defaultConfig(FFinstance* instance)
ffStrbufInitA(&instance->config.libOpenCL, 0);
ffStrbufInitA(&instance->config.libcJSON, 0);
ffStrbufInitA(&instance->config.libfreetype, 0);
ffStrbufInit(&instance->config.libAlsa);
ffStrbufInit(&instance->config.libPulse);
ffStrbufInit(&instance->config.libwlanapi);
ffStrbufInit(&instance->config.libnm);
@@ -408,7 +408,7 @@ static void destroyConfig(FFinstance* instance)
ffStrbufDestroy(&instance->config.libOpenCL);
ffStrbufDestroy(&instance->config.libcJSON);
ffStrbufDestroy(&instance->config.libfreetype);
ffStrbufDestroy(&instance->config.libAlsa);
ffStrbufDestroy(&instance->config.libPulse);
ffStrbufDestroy(&instance->config.libwlanapi);
ffStrbufDestroy(&instance->config.libnm);
@@ -514,8 +514,8 @@ void ffListFeatures()
#ifdef FF_HAVE_FREETYPE
"freetype\n"
#endif
#ifdef FF_HAVE_ALSA
"alsa-lib\n"
#ifdef FF_HAVE_PULSE
"libpulse\n"
#endif
#ifdef FF_HAVE_LIBNM
"libnm\n"
+1
View File
@@ -428,3 +428,4 @@
#--lib-cjson /usr/lib/libcjson.so
#--lib-cjson /usr/lib/libasound.so
#--lib-freetype /data/data/com.termux/files/usr/lib
#--lib-pulse /usr/lib/libpulse.so
+1 -1
View File
@@ -98,7 +98,7 @@ Library options: Set the path of a library to load
--lib-osmesa <path>
--lib-opencl <path>
--lib-cjson <path>
--lib-alsa <path>
--lib-pulse <path>
--lib-freetype <path>
Module specific options:
+1
View File
@@ -11,6 +11,7 @@ typedef struct FFSoundDevice
FFstrbuf manufacturer;
uint8_t volume; // 0-100%
bool main;
bool active;
} FFSoundDevice;
const char* ffDetectSound(const FFinstance* instance, FFlist* devices /* List of FFSoundDevice */);
+1
View File
@@ -38,6 +38,7 @@ const char* ffDetectSound(FF_MAYBE_UNUSED const FFinstance* instance, FFlist* de
FFSoundDevice* device = (FFSoundDevice*) ffListAdd(devices);
device->main = deviceId == mainDeviceId;
device->active = device->main;
device->volume = 0;
ffStrbufInit(&device->name);
ffStrbufInit(&device->manufacturer);
+107 -62
View File
@@ -1,89 +1,134 @@
#include "sound.h"
#ifdef FF_HAVE_ALSA
#ifdef FF_HAVE_PULSE
#include <common/library.h>
#include <pulse/pulseaudio.h>
#include "common/library.h"
#include "common/io.h"
#include <alsa/asoundlib.h>
static const char* doDetectSound(const FFinstance* instance, FFlist* devices)
static void paSinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
{
FF_LIBRARY_LOAD(asound, &instance->config.libAlsa, "dlopen asound failed", "libasound" FF_LIBRARY_EXTENSION, 2);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_open);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_attach);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_selem_register);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_load);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_first_elem);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_elem_next);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_selem_is_active);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_selem_get_playback_volume_range);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_selem_get_playback_volume);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_selem_get_name);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(asound, snd_mixer_close);
FF_UNUSED(c);
snd_mixer_t *mixer;
if (ffsnd_mixer_open(&mixer, 0) < 0)
return "snd_mixer_open() failed";
if(eol > 0 || !i)
return;
if (ffsnd_mixer_attach(mixer, "default") < 0)
FFSoundDevice* device = ffListAdd(userdata);
ffStrbufInitS(&device->name, i->description);
ffStrbufInitS(&device->manufacturer, i->name);
device->volume = i->mute ? 0 : (uint8_t) (i->volume.values[0] * 100 / PA_VOLUME_NORM);
device->active = i->active_port && i->active_port->available == PA_PORT_AVAILABLE_YES;
device->main = false;
}
static void paServerInfoCallback(pa_context *c, const pa_server_info *i, void *userdata)
{
FF_UNUSED(c);
if(!i)
return;
FF_LIST_FOR_EACH(FFSoundDevice, device, *(FFlist*)userdata)
{
ffsnd_mixer_close(mixer);
return "snd_mixer_attach(\"default\") failed";
if(ffStrbufEqualS(&device->manufacturer, i->default_sink_name))
{
device->main = true;
break;
}
}
}
static const char* detectSound(const FFinstance* instance, FFlist* devices)
{
FF_LIBRARY_LOAD(pulse, &instance->config.libPulse, "Failed to load libpulse.so.0", "libpulse.so.0", 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_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";
}
if (ffsnd_mixer_selem_register(mixer, NULL, NULL) < 0)
pa_context* context = ffpa_context_new(mainloopApi, "fastfetch");
if(!context)
{
ffsnd_mixer_close(mixer);
return "snd_mixer_selem_register() failed";
ffpa_mainloop_free(mainloop);
return "Failed to create pulseaudio context";
}
if (ffsnd_mixer_load(mixer) < 0)
if(ffpa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
{
ffsnd_mixer_close(mixer);
return "snd_mixer_load() failed";
ffpa_context_unref(context);
ffpa_mainloop_free(mainloop);
return "Failed to connect to pulseaudio context";
}
for (snd_mixer_elem_t* elem = ffsnd_mixer_first_elem(mixer); elem; elem = ffsnd_mixer_elem_next(elem))
int state;
while((state = ffpa_context_get_state(context)) != PA_CONTEXT_READY)
{
if (!ffsnd_mixer_selem_is_active(elem))
continue;
if(!PA_CONTEXT_IS_GOOD(state))
{
ffpa_context_unref(context);
ffpa_mainloop_free(mainloop);
return "Failed to get pulseaudio context state";
}
long min, max;
if (ffsnd_mixer_selem_get_playback_volume_range(elem, &min, &max) < 0)
continue;
long volume;
if (ffsnd_mixer_selem_get_playback_volume(elem, 0, &volume) < 0)
continue;
const char* name = ffsnd_mixer_selem_get_name(elem);
FFSoundDevice* device = (FFSoundDevice*) ffListAdd(devices);
device->main = strcmp(name, "Master") == 0;
device->volume = (uint8_t) ((double)(volume - min) * 100.0 / (double)(max - min) + 0.5);
ffStrbufInitS(&device->name, name);
ffStrbufInit(&device->manufacturer);
ffpa_mainloop_iterate(mainloop, 1, NULL);
}
ffsnd_mixer_close(mixer);
pa_operation* operation = ffpa_context_get_sink_info_list(context, paSinkInfoCallback, devices);
if(!operation)
{
ffpa_context_unref(context);
ffpa_mainloop_free(mainloop);
return "Failed to get pulseaudio sink info list";
}
while(ffpa_operation_get_state(operation) == PA_OPERATION_RUNNING)
ffpa_mainloop_iterate(mainloop, 1, NULL);
ffpa_operation_unref(operation);
operation = ffpa_context_get_server_info(context, paServerInfoCallback, devices);
if(!operation)
{
ffpa_context_unref(context);
ffpa_mainloop_free(mainloop);
return "Failed to get pulseaudio server info";
}
while(ffpa_operation_get_state(operation) == PA_OPERATION_RUNNING)
ffpa_mainloop_iterate(mainloop, 1, NULL);
ffpa_operation_unref(operation);
ffpa_context_unref(context);
ffpa_mainloop_free(mainloop);
return NULL;
}
#endif // FF_HAVE_ALSA
#endif // FF_HAVE_PULSE
const char* ffDetectSound(FF_MAYBE_UNUSED const FFinstance* instance, FF_MAYBE_UNUSED FFlist* devices /* List of FFSoundDevice */)
const char* ffDetectSound(const FFinstance* instance, FFlist* devices)
{
#ifdef FF_HAVE_ALSA
ffSuppressIO(true);
const char* error = doDetectSound(instance, devices);
ffSuppressIO(false);
return error;
#ifdef FF_HAVE_PULSE
return detectSound(instance, devices);
#else
return "Fastfetch was built without alsa support";
FF_UNUSED(instance, devices);
return "Fastfetch was built without libpulse support";
#endif
}
+1
View File
@@ -57,6 +57,7 @@ const char* ffDetectSound(FF_MAYBE_UNUSED const FFinstance* instance, FF_MAYBE_U
FFSoundDevice* device = (FFSoundDevice*) ffListAdd(devices);
device->main = wcscmp(mainDeviceId, immDeviceId) == 0;
device->active = device->main;
device->volume = 0;
ffStrbufInit(&device->name);
ffStrbufInit(&device->manufacturer);
+2
View File
@@ -1275,6 +1275,8 @@ static void parseOption(FFinstance* instance, FFdata* data, const char* key, con
optionParseString(key, value, &instance->config.libcJSON);
else if(strcasecmp(subkey, "-wlanapi") == 0)
optionParseString(key, value, &instance->config.libwlanapi);
else if(strcasecmp(key, "-pulse") == 0)
optionParseString(key, value, &instance->config.libPulse);
else if(strcasecmp(subkey, "-nm") == 0)
optionParseString(key, value, &instance->config.libnm);
else
+1 -1
View File
@@ -164,7 +164,7 @@ typedef struct FFconfig
FFstrbuf libOpenCL;
FFstrbuf libcJSON;
FFstrbuf libfreetype;
FFstrbuf libAlsa;
FFstrbuf libPulse;
FFstrbuf libwlanapi;
FFstrbuf libnm;
+30 -26
View File
@@ -15,10 +15,11 @@ static void printDevice(FFinstance* instance, const FFSoundDevice* device, uint8
printf(" (%d%%)", device->volume);
else
fputs(" (muted)", stdout);
if(device->main && instance->config.soundShowAll)
puts(" (*)");
else
putchar('\n');
if(device->main && index > 0)
fputs(" (*)", stdout);
putchar('\n');
}
else
{
@@ -31,6 +32,30 @@ static void printDevice(FFinstance* instance, const FFSoundDevice* device, uint8
}
}
static void printSound(FFinstance* instance, FFlist* devices)
{
FF_LIST_AUTO_DESTROY filtered;
ffListInit(&filtered, sizeof(FFSoundDevice*));
FF_LIST_FOR_EACH(FFSoundDevice, device, *devices)
{
if(!device->active && !instance->config.soundShowAll)
continue;
*(FFSoundDevice**)ffListAdd(&filtered) = device;
}
if(filtered.length == 0)
{
ffPrintError(instance, FF_SOUND_MODULE_NAME, 0, &instance->config.sound, "No active sound devices found");
return;
}
uint8_t index = 1;
FF_LIST_FOR_EACH(FFSoundDevice*, device, filtered)
printDevice(instance, *device, filtered.length == 1 ? 0 : index++);
}
void ffPrintSound(FFinstance* instance)
{
FF_LIST_AUTO_DESTROY result;
@@ -43,28 +68,7 @@ void ffPrintSound(FFinstance* instance)
return;
}
if(result.length == 0)
{
ffPrintError(instance, FF_SOUND_MODULE_NAME, 0, &instance->config.sound, "No sound devices found");
return;
}
if(instance->config.soundShowAll)
{
uint8_t index = 0;
FF_LIST_FOR_EACH(FFSoundDevice, device, result)
{
printDevice(instance, device, result.length == 1 ? 0 : ++index);
}
}
else
{
FF_LIST_FOR_EACH(FFSoundDevice, device, result)
{
if (!device->main) continue;
printDevice(instance, device, 0);
}
}
printSound(instance, &result);
FF_LIST_FOR_EACH(FFSoundDevice, device, result)
{