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};