master ohmu/fastfetch-openbsd / src / modules / sound / sound.c
ohmu  ·  2026-10-15
  1#include "common/percent.h"
  2#include "common/printing.h"
  3#include "common/jsonconfig.h"
  4#include "common/strutil.h"
  5#include "detection/sound/sound.h"
  6#include "modules/sound/sound.h"
  7
  8static void printDevice(FFSoundOptions* options, const FFSoundDevice* device, uint8_t index) {
  9    FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;
 10    if (options->moduleArgs.outputFormat.length == 0) {
 11        ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Sound), index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
 12
 13        FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate();
 14        if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) {
 15            ffStrbufAppend(&str, &device->name);
 16        }
 17
 18        if (device->volume != FF_SOUND_VOLUME_UNKNOWN) {
 19            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) {
 20                if (str.length) {
 21                    ffStrbufAppendC(&str, ' ');
 22                }
 23
 24                ffPercentAppendBar(&str, device->volume, options->percent, &options->moduleArgs);
 25            }
 26
 27            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) {
 28                if (str.length) {
 29                    ffStrbufAppendC(&str, ' ');
 30                }
 31
 32                ffPercentAppendNum(&str, device->volume, options->percent, str.length > 0, &options->moduleArgs);
 33            }
 34        }
 35
 36        if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) {
 37            if ((device->type & FF_SOUND_TYPE_MAIN) && index > 0) {
 38                ffStrbufAppendS(&str, " (*)");
 39            }
 40        }
 41
 42        ffStrbufPutTo(&str, stdout);
 43    } else {
 44        FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate();
 45        FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate();
 46        if (device->volume != FF_SOUND_VOLUME_UNKNOWN) {
 47            if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) {
 48                ffPercentAppendNum(&percentageNum, device->volume, options->percent, false, &options->moduleArgs);
 49            }
 50            if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) {
 51                ffPercentAppendBar(&percentageBar, device->volume, options->percent, &options->moduleArgs);
 52            }
 53        }
 54
 55        bool isMain = !!(device->type & FF_SOUND_TYPE_MAIN);
 56        bool isActive = !!(device->type & FF_SOUND_TYPE_ACTIVE);
 57        FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Sound), index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {
 58                                                                                                              FF_ARG(isMain, "is-main"),
 59                                                                                                              FF_ARG(isActive, "is-active"),
 60                                                                                                              FF_ARG(device->name, "name"),
 61                                                                                                              FF_ARG(percentageNum, "volume-percentage"),
 62                                                                                                              FF_ARG(device->identifier, "identifier"),
 63                                                                                                              FF_ARG(percentageBar, "volume-percentage-bar"),
 64                                                                                                              FF_ARG(device->platformApi, "platform-api"),
 65                                                                                                          }));
 66    }
 67}
 68
 69bool ffPrintSound(FFSoundOptions* options) {
 70    FF_LIST_AUTO_DESTROY result = ffListCreate();
 71
 72    const char* error = ffDetectSound(options, &result);
 73
 74    if (error) {
 75        ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Sound), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
 76        return false;
 77    }
 78
 79    if (result.length == 0) {
 80        ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Sound), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No matched sound devices found");
 81        return false;
 82    }
 83
 84    uint8_t index = 1;
 85    FF_LIST_FOR_EACH (FFSoundDevice, device, result) {
 86        printDevice(options, device, result.length == 1 ? 0 : index++);
 87    }
 88
 89    FF_LIST_FOR_EACH (FFSoundDevice, device, result) {
 90        ffStrbufDestroy(&device->identifier);
 91        ffStrbufDestroy(&device->name);
 92        ffStrbufDestroy(&device->platformApi);
 93    }
 94
 95    return true;
 96}
 97
 98void ffParseSoundJsonObject(FFSoundOptions* options, yyjson_val* module) {
 99    yyjson_val *key, *val;
100    size_t idx, max;
101    yyjson_obj_foreach (module, idx, max, key, val) {
102        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) {
103            continue;
104        }
105
106        if (unsafe_yyjson_equals_str(key, "soundType")) {
107            int value;
108            const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
109                                                                       { "main", FF_SOUND_TYPE_MAIN },
110                                                                       { "active", FF_SOUND_TYPE_ACTIVE },
111                                                                       { "all", FF_SOUND_TYPE_NONE }, // Don't filter devices
112                                                                       {},
113                                                                   });
114            if (error) {
115                ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Sound), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
116            } else {
117                options->soundType = (FFSoundType) value;
118            }
119            continue;
120        }
121
122        if (ffPercentParseJsonObject(key, val, &options->percent)) {
123            continue;
124        }
125
126        ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Sound), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
127    }
128}
129
130[[gnu::cold]]
131void ffGenerateSoundJsonConfig(FFSoundOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
132    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
133
134    switch (options->soundType) {
135        case FF_SOUND_TYPE_MAIN:
136            yyjson_mut_obj_add_str(doc, module, "soundType", "main");
137            break;
138        case FF_SOUND_TYPE_ACTIVE:
139            yyjson_mut_obj_add_str(doc, module, "soundType", "active");
140            break;
141        case FF_SOUND_TYPE_NONE:
142            yyjson_mut_obj_add_str(doc, module, "soundType", "all");
143            break;
144    }
145
146    ffPercentGenerateJsonConfig(doc, module, options->percent);
147}
148
149bool ffGenerateSoundJsonResult(FFSoundOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
150    FF_LIST_AUTO_DESTROY result = ffListCreate();
151    const char* error = ffDetectSound(options, &result);
152
153    if (error) {
154        yyjson_mut_obj_add_str(doc, module, "error", error);
155        return false;
156    }
157
158    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result");
159    FF_LIST_FOR_EACH (FFSoundDevice, item, result) {
160        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);
161
162        yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name);
163        yyjson_mut_obj_add_strbuf(doc, obj, "identifier", &item->identifier);
164        yyjson_mut_obj_add_strbuf(doc, obj, "platformApi", &item->platformApi);
165
166        yyjson_mut_val* type = yyjson_mut_obj_add_arr(doc, obj, "type");
167        if (item->type & FF_SOUND_TYPE_MAIN) {
168            yyjson_mut_arr_add_str(doc, type, "main");
169        }
170        if (item->type & FF_SOUND_TYPE_ACTIVE) {
171            yyjson_mut_arr_add_str(doc, type, "active");
172        }
173
174        if (item->volume != FF_SOUND_VOLUME_UNKNOWN) {
175            yyjson_mut_obj_add_uint(doc, obj, "volume", item->volume);
176        } else {
177            yyjson_mut_obj_add_null(doc, obj, "volume");
178        }
179    }
180
181    FF_LIST_FOR_EACH (FFSoundDevice, device, result) {
182        ffStrbufDestroy(&device->identifier);
183        ffStrbufDestroy(&device->name);
184        ffStrbufDestroy(&device->platformApi);
185    }
186
187    return true;
188}
189
190void ffInitSoundOptions(FFSoundOptions* options) {
191    ffOptionInitModuleArg(&options->moduleArgs, "");
192
193    options->soundType = FF_SOUND_TYPE_MAIN;
194    options->percent = (FFPercentageModuleConfig) { 80, 90, 0 };
195}
196
197void ffDestroySoundOptions(FFSoundOptions* options) {
198    ffOptionDestroyModuleArg(&options->moduleArgs);
199}
200
201FFModuleBaseInfo ffSoundModuleInfo = {
202    .name = "Sound",
203    .description = "Print sound devices, volume levels, etc",
204    .displayName = {
205        .en = "Sound",
206        .ar = "الصوت",
207        .cs = "Zvuk",
208        .de = "Sound",
209        .es = "Sonido",
210        .fi = "Ääni",
211        .fr = "Son",
212        .gl = "Son",
213        .he = "קול",
214        .id = "Suara",
215        .it = "Audio",
216        .ja = "サウンド",
217        .ko = "사운드",
218        .pl = "Dźwięk",
219        .pt = "Som",
220        .ru = "Звук",
221        .tr = "Ses",
222        .uk = "Звук",
223        .vi = "Âm thanh",
224        .zh_CN = "声音",
225        .zh_TW = "聲音",
226    },
227    .initOptions = (void*) ffInitSoundOptions,
228    .destroyOptions = (void*) ffDestroySoundOptions,
229    .parseJsonObject = (void*) ffParseSoundJsonObject,
230    .printModule = (void*) ffPrintSound,
231    .generateJsonResult = (void*) ffGenerateSoundJsonResult,
232    .generateJsonConfig = (void*) ffGenerateSoundJsonConfig,
233    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
234        { "Is main sound device", "is-main" },
235        { "Is active sound device", "is-active" },
236        { "Device name", "name" },
237        { "Volume (in percentage num)", "volume-percentage" },
238        { "Identifier", "identifier" },
239        { "Volume (in percentage bar)", "volume-percentage-bar" },
240        { "Platform API used", "platform-api" },
241    })),
242    .defaultOrder = 61,
243};