diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d51173fa..927448737 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index 4ebf8ac58..34ba5e726 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/completions/bash b/completions/bash index 2b6db37c6..93386be34 100644 --- a/completions/bash +++ b/completions/bash @@ -379,7 +379,7 @@ __fastfetch_completion() "--lib-glx" "--lib-osmesa" "--lib-opencl" - "--lib-alsa" + "--lib-pulse" "--battery-dir" ) diff --git a/src/common/init.c b/src/common/init.c index fc47c0888..7eabe0948 100644 --- a/src/common/init.c +++ b/src/common/init.c @@ -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" diff --git a/src/data/config_user.txt b/src/data/config_user.txt index 5ace73f0b..1b9ac9635 100644 --- a/src/data/config_user.txt +++ b/src/data/config_user.txt @@ -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 diff --git a/src/data/help.txt b/src/data/help.txt index a8325cdc4..c4fd1acd3 100644 --- a/src/data/help.txt +++ b/src/data/help.txt @@ -98,7 +98,7 @@ Library options: Set the path of a library to load --lib-osmesa --lib-opencl --lib-cjson - --lib-alsa + --lib-pulse --lib-freetype Module specific options: diff --git a/src/detection/sound/sound.h b/src/detection/sound/sound.h index ecbc53db5..6247ed299 100644 --- a/src/detection/sound/sound.h +++ b/src/detection/sound/sound.h @@ -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 */); diff --git a/src/detection/sound/sound_apple.c b/src/detection/sound/sound_apple.c index 8ede08f28..78ab54740 100644 --- a/src/detection/sound/sound_apple.c +++ b/src/detection/sound/sound_apple.c @@ -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); diff --git a/src/detection/sound/sound_linux.c b/src/detection/sound/sound_linux.c index bf5a421d0..ef7cd2f00 100644 --- a/src/detection/sound/sound_linux.c +++ b/src/detection/sound/sound_linux.c @@ -1,89 +1,134 @@ #include "sound.h" -#ifdef FF_HAVE_ALSA +#ifdef FF_HAVE_PULSE +#include +#include -#include "common/library.h" -#include "common/io.h" - -#include - -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 } diff --git a/src/detection/sound/sound_windows.cpp b/src/detection/sound/sound_windows.cpp index a75c800e1..23f094feb 100644 --- a/src/detection/sound/sound_windows.cpp +++ b/src/detection/sound/sound_windows.cpp @@ -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); diff --git a/src/fastfetch.c b/src/fastfetch.c index 1febd5f8b..c49a3d408 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -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 diff --git a/src/fastfetch.h b/src/fastfetch.h index ffd30d30c..e1d5be954 100644 --- a/src/fastfetch.h +++ b/src/fastfetch.h @@ -164,7 +164,7 @@ typedef struct FFconfig FFstrbuf libOpenCL; FFstrbuf libcJSON; FFstrbuf libfreetype; - FFstrbuf libAlsa; + FFstrbuf libPulse; FFstrbuf libwlanapi; FFstrbuf libnm; diff --git a/src/modules/sound.c b/src/modules/sound.c index 8c2b82395..3bc478145 100644 --- a/src/modules/sound.c +++ b/src/modules/sound.c @@ -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) {