ohmu
·
2026-10-15
1#include "fastfetch.h"
2#include "common/color.h"
3#include "common/io.h"
4#include "common/jsonconfig.h"
5#include "common/percent.h"
6#include "common/strutil.h"
7#include "options/display.h"
8
9#if !FF_MODULE_DISABLE_TERMINALTHEME
10 #include "detection/terminaltheme/terminaltheme.h"
11#endif
12#if !FF_MODULE_DISABLE_LOCALE
13 #include "detection/locale/locale.h"
14#endif
15
16#include <unistd.h>
17
18// @returns offsetof(FFModuleDisplayName, <language>)
19// @see src/common/option.h
20static uint32_t optionParseLanguageString(FFstrbuf* language) {
21 const bool detected = language->length == 0;
22
23 if (detected) {
24#if !FF_MODULE_DISABLE_LOCALE
25 const char* error = ffDetectLocale(language);
26 if (__builtin_expect(error != nullptr, false)) {
27 fprintf(stderr, "ffDetectLocale: %s\n", error);
28 exit(477);
29 }
30#else
31 fputs("Error: --key-language requires a value\n", stderr);
32 exit(477);
33#endif
34 }
35
36 ffStrbufSubstrBeforeFirstC(language, '.'); // Remove the encoding part of the locale
37 #if __linux__
38 ffStrbufSubstrBeforeFirstC(language, '@'); // Remove the modifier part of the locale (Linux only)
39 #endif
40 ffStrbufLowerCase(language);
41
42 if (language->length <= 1) {
43 if (language->chars[0] == 'c') {
44 return offsetof(FFModuleDisplayName, en);
45 } else {
46 goto error;
47 }
48 } else if (language->length > 2) {
49 if (language->chars[2] == '-') {
50 language->chars[2] = '_';
51 } else if (__builtin_expect(language->chars[2] != '_', false)) {
52 goto error;
53 }
54 }
55
56 // The language code is expected to be in the format of "en", "en_US", "de", "de_DE", etc.
57 // First try to match the full language code, then try to match just the first two characters (the language part).
58 // If no match is found, report a language not supported error, unless the language was auto-detected.
59 // en_US -> offsetof(FFModuleDisplayName, en)
60 // en -> offsetof(FFModuleDisplayName, en)
61 // zh_CN -> offsetof(FFModuleDisplayName, zh_CN)
62 // zh -> Error: Language 'zh' is not supported. This is an intentional design decision, as the language code 'zh' could refer to either Simplified or Traditional Chinese.
63
64#pragma GCC diagnostic push
65#pragma GCC diagnostic ignored "-Wmultichar"
66 switch (language->chars[0] << 8 | language->chars[1]) {
67 case 'en': return offsetof(FFModuleDisplayName, en);
68 case 'ar': return offsetof(FFModuleDisplayName, ar);
69 case 'cs': return offsetof(FFModuleDisplayName, cs);
70 case 'de': return offsetof(FFModuleDisplayName, de);
71 case 'es': return offsetof(FFModuleDisplayName, es);
72 case 'fi': return offsetof(FFModuleDisplayName, fi);
73 case 'fr': return offsetof(FFModuleDisplayName, fr);
74 case 'gl': return offsetof(FFModuleDisplayName, gl);
75 case 'he': return offsetof(FFModuleDisplayName, he);
76 case 'id': return offsetof(FFModuleDisplayName, id);
77 case 'it': return offsetof(FFModuleDisplayName, it);
78 case 'ja': return offsetof(FFModuleDisplayName, ja);
79 case 'ko': return offsetof(FFModuleDisplayName, ko);
80 case 'pl': return offsetof(FFModuleDisplayName, pl);
81 case 'pt': return offsetof(FFModuleDisplayName, pt);
82 case 'ru': return offsetof(FFModuleDisplayName, ru);
83 case 'tr': return offsetof(FFModuleDisplayName, tr);
84 case 'uk': return offsetof(FFModuleDisplayName, uk);
85 case 'vi': return offsetof(FFModuleDisplayName, vi);
86 case 'zh': {
87 if (ffStrbufEqualS(language, "zh_cn")) {
88 return offsetof(FFModuleDisplayName, zh_CN);
89 } else if (ffStrbufEqualS(language, "zh_tw")) {
90 return offsetof(FFModuleDisplayName, zh_TW);
91 }
92 goto error;
93 }
94 }
95#pragma GCC diagnostic pop
96
97error:
98 // The user asked for the system locale, not for this specific language. Most locales have no
99 // translation, so falling back to English is friendlier than aborting fastfetch entirely.
100 if (detected) return offsetof(FFModuleDisplayName, en);
101
102 fprintf(stderr, "Error: Language '%s' is not supported\n", language->chars);
103 exit(477);
104}
105
106const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_val* root, yyjson_val** pkey) {
107 yyjson_val* object = yyjson_obj_get(root, "display");
108 if (!object) {
109 return nullptr;
110 }
111 if (!yyjson_is_obj(object)) {
112 return "Property 'display' must be an object";
113 }
114
115 yyjson_val *key, *val;
116 size_t idx, max;
117 yyjson_obj_foreach (object, idx, max, key, val) {
118 *pkey = key;
119 if (unsafe_yyjson_equals_str(key, "stat")) {
120 if (yyjson_is_bool(val)) {
121 if (yyjson_get_bool(val)) {
122 options->stat = 0;
123 options->showErrors = true;
124 } else {
125 options->stat = -1;
126 }
127 } else if (yyjson_is_uint(val)) {
128 options->stat = (int) yyjson_get_uint(val);
129 options->showErrors = true;
130 } else {
131 return "display.stat must be a boolean or a positive integer";
132 }
133 } else if (unsafe_yyjson_equals_str(key, "pipe")) {
134 options->pipe = yyjson_get_bool(val);
135 } else if (unsafe_yyjson_equals_str(key, "showErrors")) {
136 options->showErrors = yyjson_get_bool(val);
137 } else if (unsafe_yyjson_equals_str(key, "disableLinewrap")) {
138 options->disableLinewrap = yyjson_get_bool(val);
139 } else if (unsafe_yyjson_equals_str(key, "hideCursor")) {
140 options->hideCursor = yyjson_get_bool(val);
141 } else if (unsafe_yyjson_equals_str(key, "separator")) {
142 ffStrbufSetJsonVal(&options->keyValueSeparator, val);
143 } else if (unsafe_yyjson_equals_str(key, "color")) {
144 if (yyjson_is_str(val)) {
145 ffOptionParseColor(unsafe_yyjson_get_str(val), &options->colorKeys);
146 ffStrbufSet(&options->colorTitle, &options->colorKeys);
147 } else if (yyjson_is_obj(val)) {
148 yyjson_val* colorKeys = yyjson_obj_get(val, "keys");
149 if (colorKeys) {
150 ffOptionParseColor(yyjson_get_str(colorKeys), &options->colorKeys);
151 }
152 yyjson_val* colorTitle = yyjson_obj_get(val, "title");
153 if (colorTitle) {
154 ffOptionParseColor(yyjson_get_str(colorTitle), &options->colorTitle);
155 }
156 yyjson_val* colorOutput = yyjson_obj_get(val, "output");
157 if (colorOutput) {
158 ffOptionParseColor(yyjson_get_str(colorOutput), &options->colorOutput);
159 }
160 yyjson_val* colorSeparator = yyjson_obj_get(val, "separator");
161 if (colorSeparator) {
162 ffOptionParseColor(yyjson_get_str(colorSeparator), &options->colorSeparator);
163 }
164 } else {
165 return "display.color must be either a string or an object";
166 }
167 } else if (unsafe_yyjson_equals_str(key, "brightColor")) {
168 options->brightColor = yyjson_get_bool(val);
169 } else if (unsafe_yyjson_equals_str(key, "duration")) {
170 if (!yyjson_is_obj(val)) {
171 return "display.duration must be an object";
172 }
173
174 yyjson_val* abbreviation = yyjson_obj_get(val, "abbreviation");
175 if (abbreviation) {
176 options->durationAbbreviation = yyjson_get_bool(abbreviation);
177 }
178
179 yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
180 if (spaceBeforeUnit) {
181 int value;
182 const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
183 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
184 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
185 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
186 {},
187 });
188 if (error) {
189 return error;
190 }
191 options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
192 }
193 } else if (unsafe_yyjson_equals_str(key, "size")) {
194 if (!yyjson_is_obj(val)) {
195 return "display.size must be an object";
196 }
197
198 yyjson_val* maxPrefix = yyjson_obj_get(val, "maxPrefix");
199 if (maxPrefix) {
200 int value;
201 const char* error = ffJsonConfigParseEnum(
202 maxPrefix,
203 &value,
204 (FFKeyValuePair[]) {
205 { "B", 0 },
206 { "kB", 1 },
207 { "MB", 2 },
208 { "GB", 3 },
209 { "TB", 4 },
210 { "PB", 5 },
211 { "EB", 6 },
212 { "ZB", 7 },
213 { "YB", 8 },
214 {},
215 });
216 if (error) {
217 return error;
218 }
219 options->sizeMaxPrefix = (uint8_t) value;
220 }
221
222 yyjson_val* binaryPrefix = yyjson_obj_get(val, "binaryPrefix");
223 if (binaryPrefix) {
224 int value;
225 const char* error = ffJsonConfigParseEnum(binaryPrefix, &value, (FFKeyValuePair[]) {
226 { "iec", FF_SIZE_BINARY_PREFIX_TYPE_IEC },
227 { "si", FF_SIZE_BINARY_PREFIX_TYPE_SI },
228 { "jedec", FF_SIZE_BINARY_PREFIX_TYPE_JEDEC },
229 {},
230 });
231 if (error) {
232 return error;
233 }
234 options->sizeBinaryPrefix = (FFSizeBinaryPrefixType) value;
235 }
236
237 yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
238 if (ndigits) {
239 if (!yyjson_is_uint(ndigits)) {
240 return "display.size.ndigits must be an unsigned integer";
241 }
242 uint64_t val = yyjson_get_uint(ndigits);
243 if (val > 9) {
244 return "display.size.ndigits must be between 0 and 9";
245 }
246 options->sizeNdigits = (uint8_t) val;
247 }
248
249 yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
250 if (spaceBeforeUnit) {
251 int value;
252 const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
253 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
254 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
255 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
256 {},
257 });
258 if (error) {
259 return error;
260 }
261 options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
262 }
263 } else if (unsafe_yyjson_equals_str(key, "temp")) {
264 if (!yyjson_is_obj(val)) {
265 return "display.temp must be an object";
266 }
267
268 yyjson_val* unit = yyjson_obj_get(val, "unit");
269 if (unit) {
270 int value;
271 const char* error = ffJsonConfigParseEnum(unit, &value, (FFKeyValuePair[]) {
272 { "DEFAULT", FF_TEMPERATURE_UNIT_DEFAULT },
273 { "D", FF_TEMPERATURE_UNIT_DEFAULT },
274 { "CELSIUS", FF_TEMPERATURE_UNIT_CELSIUS },
275 { "C", FF_TEMPERATURE_UNIT_CELSIUS },
276 { "FAHRENHEIT", FF_TEMPERATURE_UNIT_FAHRENHEIT },
277 { "F", FF_TEMPERATURE_UNIT_FAHRENHEIT },
278 { "KELVIN", FF_TEMPERATURE_UNIT_KELVIN },
279 { "K", FF_TEMPERATURE_UNIT_KELVIN },
280 {},
281 });
282 if (error) {
283 return error;
284 }
285 options->tempUnit = (FFTemperatureUnit) value;
286 }
287
288 yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
289 if (ndigits) {
290 if (!yyjson_is_uint(ndigits)) {
291 return "display.temp.ndigits must be an unsigned integer";
292 }
293 uint64_t val = yyjson_get_uint(ndigits);
294 if (val > 9) {
295 return "display.temp.ndigits must be between 0 and 9";
296 }
297 options->tempNdigits = (uint8_t) val;
298 }
299
300 yyjson_val* color = yyjson_obj_get(val, "color");
301 if (color) {
302 if (!yyjson_is_obj(color)) {
303 return "display.temp.color must be an object";
304 }
305
306 yyjson_val* green = yyjson_obj_get(color, "green");
307 if (green) {
308 ffOptionParseColor(yyjson_get_str(green), &options->tempColorGreen);
309 }
310
311 yyjson_val* yellow = yyjson_obj_get(color, "yellow");
312 if (yellow) {
313 ffOptionParseColor(yyjson_get_str(yellow), &options->tempColorYellow);
314 }
315
316 yyjson_val* red = yyjson_obj_get(color, "red");
317 if (red) {
318 ffOptionParseColor(yyjson_get_str(red), &options->tempColorRed);
319 }
320 }
321
322 yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
323 if (spaceBeforeUnit) {
324 int value;
325 const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
326 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
327 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
328 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
329 {},
330 });
331 if (error) {
332 return error;
333 }
334 options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
335 }
336 } else if (unsafe_yyjson_equals_str(key, "percent")) {
337 if (!yyjson_is_obj(val)) {
338 return "display.percent must be an object";
339 }
340
341 yyjson_val* type = yyjson_obj_get(val, "type");
342 if (type) {
343 const char* error = ffPercentParseTypeJsonConfig(type, &options->percentType);
344 if (error) {
345 return error;
346 }
347 }
348
349 yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
350 if (ndigits) {
351 if (!yyjson_is_uint(ndigits)) {
352 return "display.percent.ndigits must be an unsigned integer";
353 }
354 uint64_t val = yyjson_get_uint(ndigits);
355 if (val > 9) {
356 return "display.percent.ndigits must be between 0 and 9";
357 }
358 options->percentNdigits = (uint8_t) val;
359 }
360
361 yyjson_val* color = yyjson_obj_get(val, "color");
362 if (color) {
363 if (!yyjson_is_obj(color)) {
364 return "display.percent.color must be an object";
365 }
366
367 yyjson_val* green = yyjson_obj_get(color, "green");
368 if (green) {
369 ffOptionParseColor(yyjson_get_str(green), &options->percentColorGreen);
370 }
371
372 yyjson_val* yellow = yyjson_obj_get(color, "yellow");
373 if (yellow) {
374 ffOptionParseColor(yyjson_get_str(yellow), &options->percentColorYellow);
375 }
376
377 yyjson_val* red = yyjson_obj_get(color, "red");
378 if (red) {
379 ffOptionParseColor(yyjson_get_str(red), &options->percentColorRed);
380 }
381 }
382
383 yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
384 if (spaceBeforeUnit) {
385 int value;
386 const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
387 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
388 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
389 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
390 {},
391 });
392 if (error) {
393 return error;
394 }
395 options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
396 }
397
398 yyjson_val* width = yyjson_obj_get(val, "width");
399 if (width) {
400 options->percentWidth = (uint8_t) yyjson_get_uint(width);
401 }
402 } else if (unsafe_yyjson_equals_str(key, "bar")) {
403 if (yyjson_is_obj(val)) {
404 yyjson_val* char_ = yyjson_obj_get(val, "char");
405 if (char_) {
406 if (!yyjson_is_obj(char_)) {
407 return "display.bar.char must be an object";
408 }
409
410 yyjson_val* charElapsed = yyjson_obj_get(char_, "elapsed");
411 if (charElapsed) {
412 ffStrbufSetJsonVal(&options->barCharElapsed, charElapsed);
413 }
414
415 yyjson_val* charTotal = yyjson_obj_get(char_, "total");
416 if (charTotal) {
417 ffStrbufSetJsonVal(&options->barCharTotal, charTotal);
418 }
419 } else {
420 yyjson_val* charElapsed = yyjson_obj_get(val, "charElapsed");
421 if (charElapsed) {
422 return "display.bar.charElapsed has been renamed to display.bar.char.elapsed.";
423 }
424
425 yyjson_val* charTotal = yyjson_obj_get(val, "charTotal");
426 if (charTotal) {
427 return "display.bar.charTotal has been renamed to display.bar.char.total.";
428 }
429 }
430
431 yyjson_val* border = yyjson_obj_get(val, "border");
432 if (border) {
433 if (yyjson_is_null(border)) {
434 ffStrbufClear(&options->barBorderLeft);
435 ffStrbufClear(&options->barBorderRight);
436 ffStrbufClear(&options->barBorderLeftElapsed);
437 ffStrbufClear(&options->barBorderRightElapsed);
438 } else {
439 if (!yyjson_is_obj(border)) {
440 return "display.bar.border must be an object";
441 }
442
443 yyjson_val* borderLeft = yyjson_obj_get(border, "left");
444 if (borderLeft) {
445 ffStrbufSetJsonVal(&options->barBorderLeft, borderLeft);
446 }
447
448 yyjson_val* borderRight = yyjson_obj_get(border, "right");
449 if (borderRight) {
450 ffStrbufSetJsonVal(&options->barBorderRight, borderRight);
451 }
452
453 yyjson_val* borderLeftElapsed = yyjson_obj_get(border, "leftElapsed");
454 if (borderLeftElapsed) {
455 ffStrbufSetJsonVal(&options->barBorderLeftElapsed, borderLeftElapsed);
456 }
457
458 yyjson_val* borderRightElapsed = yyjson_obj_get(border, "rightElapsed");
459 if (borderRightElapsed) {
460 ffStrbufSetJsonVal(&options->barBorderRightElapsed, borderRightElapsed);
461 }
462 }
463 } else {
464 yyjson_val* borderLeft = yyjson_obj_get(val, "borderLeft");
465 if (borderLeft) {
466 return "display.bar.borderLeft has been renamed to display.bar.border.left.";
467 }
468
469 yyjson_val* borderRight = yyjson_obj_get(val, "borderRight");
470 if (borderRight) {
471 return "display.bar.borderRight has been renamed to display.bar.border.right.";
472 }
473 }
474
475 yyjson_val* color = yyjson_obj_get(val, "color");
476 if (color) {
477 if (yyjson_is_null(color)) {
478 ffStrbufClear(&options->barColorElapsed);
479 ffStrbufClear(&options->barColorTotal);
480 ffStrbufClear(&options->barColorBorder);
481 } else {
482 if (!yyjson_is_obj(color)) {
483 return "display.bar.color must be an object";
484 }
485
486 yyjson_val* colorElapsed = yyjson_obj_get(color, "elapsed");
487 if (colorElapsed) {
488 const char* value = yyjson_get_str(colorElapsed);
489 if (!value) {
490 ffStrbufClear(&options->barColorElapsed);
491 } else if (ffStrEqualsIgnCase(value, "auto")) {
492 ffStrbufSetStatic(&options->barColorElapsed, "auto");
493 } else {
494 ffOptionParseColor(value, &options->barColorElapsed);
495 }
496 }
497
498 yyjson_val* colorTotal = yyjson_obj_get(color, "total");
499 if (colorTotal) {
500 ffOptionParseColor(yyjson_get_str(colorTotal), &options->barColorTotal);
501 }
502
503 yyjson_val* colorBorder = yyjson_obj_get(color, "border");
504 if (colorBorder) {
505 ffOptionParseColor(yyjson_get_str(colorBorder), &options->barColorBorder);
506 }
507 }
508 }
509
510 yyjson_val* width = yyjson_obj_get(val, "width");
511 if (width) {
512 options->barWidth = (uint8_t) yyjson_get_uint(width);
513 }
514 } else {
515 return "display.bar must be an object";
516 }
517 } else if (unsafe_yyjson_equals_str(key, "fraction")) {
518 if (yyjson_is_obj(val)) {
519 yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
520 if (ndigits) {
521 if (yyjson_is_null(ndigits)) {
522 options->fractionNdigits = -1;
523 } else {
524 if (!yyjson_is_int(ndigits)) {
525 return "display.fraction.ndigits must be an integer";
526 }
527 int64_t val = yyjson_get_int(ndigits);
528 if (val < -1 || val > 9) {
529 return "display.fraction.ndigits must be between -1 and 9";
530 }
531 options->fractionNdigits = (int8_t) val;
532 }
533 }
534 yyjson_val* trailingZeros = yyjson_obj_get(val, "trailingZeros");
535 if (trailingZeros) {
536 if (yyjson_is_null(trailingZeros)) {
537 options->fractionTrailingZeros = FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT;
538 } else {
539 int value;
540 const char* error = ffJsonConfigParseEnum(trailingZeros, &value, (FFKeyValuePair[]) {
541 { "default", FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT },
542 { "always", FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS },
543 { "never", FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER },
544 {},
545 });
546 if (error) {
547 return error;
548 }
549 options->fractionTrailingZeros = (FFFractionTrailingZerosType) value;
550 }
551 }
552 } else {
553 return "display.fraction must be an object";
554 }
555 } else if (unsafe_yyjson_equals_str(key, "noBuffer")) {
556 options->noBuffer = yyjson_get_bool(val);
557 } else if (unsafe_yyjson_equals_str(key, "key")) {
558 if (yyjson_is_obj(val)) {
559 yyjson_val* width = yyjson_obj_get(val, "width");
560 if (width) {
561 options->keyWidth = (uint16_t) yyjson_get_uint(width);
562 }
563
564 yyjson_val* type = yyjson_obj_get(val, "type");
565 if (type) {
566 int value;
567 const char* error = ffJsonConfigParseEnum(type, &value, (FFKeyValuePair[]) { { "none", FF_MODULE_KEY_TYPE_NONE }, { "string", FF_MODULE_KEY_TYPE_STRING }, { "icon", FF_MODULE_KEY_TYPE_ICON }, { "both", FF_MODULE_KEY_TYPE_BOTH }, { "both-0", FF_MODULE_KEY_TYPE_BOTH_0 }, { "both-1", FF_MODULE_KEY_TYPE_BOTH_1 }, { "both-2", FF_MODULE_KEY_TYPE_BOTH_2 }, { "both-3", FF_MODULE_KEY_TYPE_BOTH_3 }, { "both-4", FF_MODULE_KEY_TYPE_BOTH_4 }, {} });
568 if (error) {
569 return error;
570 }
571 options->keyType = (uint8_t) value;
572 }
573
574 yyjson_val* paddingLeft = yyjson_obj_get(val, "paddingLeft");
575 if (paddingLeft) {
576 options->keyPaddingLeft = (uint16_t) yyjson_get_uint(paddingLeft);
577 }
578
579 yyjson_val* language = yyjson_obj_get(val, "language");
580 if (language) {
581 FF_STRBUF_AUTO_DESTROY languageBuffer = ffStrbufCreate();
582 if (yyjson_is_str(language)) {
583 ffStrbufSetJsonVal(&languageBuffer, language);
584 } else if (!yyjson_is_null(language)) {
585 return "display.key.language must be a string or null";
586 }
587 options->keyLanguage = optionParseLanguageString(&languageBuffer);
588 }
589 } else {
590 return "display.key must be an object";
591 }
592 } else if (unsafe_yyjson_equals_str(key, "constants")) {
593 if (!yyjson_is_arr(val)) {
594 return "display.constants must be an array";
595 }
596 yyjson_val* item;
597 size_t idx, max;
598 yyjson_arr_foreach (val, idx, max, item) {
599 FFstrbuf* buffer = FF_LIST_ADD(FFstrbuf, options->constants);
600 ffStrbufInitJsonVal(buffer, item);
601 }
602 } else if (unsafe_yyjson_equals_str(key, "freq")) {
603 if (!yyjson_is_obj(val)) {
604 return "display.freq must be an object";
605 }
606
607 yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
608 if (ndigits) {
609 if (yyjson_is_null(ndigits)) {
610 options->freqNdigits = -1;
611 } else {
612 if (!yyjson_is_int(ndigits)) {
613 return "display.freq.ndigits must be an integer";
614 }
615 int64_t val = yyjson_get_int(ndigits);
616 if (val < -1 || val > 9) {
617 return "display.freq.ndigits must be between -1 and 9";
618 }
619 options->freqNdigits = (int8_t) val;
620 }
621 }
622
623 yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
624 if (spaceBeforeUnit) {
625 int value;
626 const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
627 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
628 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
629 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
630 {},
631 });
632 if (error) {
633 return error;
634 }
635 options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
636 }
637 } else if (unsafe_yyjson_equals_str(key, "common")) {
638 if (!yyjson_is_obj(val)) {
639 return "display.common must be an object";
640 }
641
642 yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
643 if (ndigits) {
644 if (!yyjson_is_uint(ndigits)) {
645 return "display.common.ndigits must be an unsigned integer";
646 }
647 uint64_t val = yyjson_get_uint(ndigits);
648 if (val > 9) {
649 return "display.common.ndigits must be between 0 and 9";
650 }
651 options->fractionNdigits = (int8_t) val;
652 options->freqNdigits = (int8_t) val;
653 options->percentNdigits = (uint8_t) val;
654 options->sizeNdigits = (uint8_t) val;
655 options->tempNdigits = (uint8_t) val;
656 }
657
658 yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
659 if (spaceBeforeUnit) {
660 int value;
661 const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
662 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
663 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
664 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
665 {},
666 });
667 if (error) {
668 return error;
669 }
670 options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
671 options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
672 options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
673 options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
674 options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
675 }
676 } else {
677 return "Unknown display property";
678 }
679 }
680
681 return nullptr;
682}
683
684static inline void optionCheckString(const char* key, const char* value, FFstrbuf* buffer) {
685 if (value == nullptr) {
686 fprintf(stderr, "Error: usage: %s <str>\n", key);
687 exit(477);
688 }
689 ffStrbufEnsureFree(buffer, 63); // This is not needed, as ffStrbufSetS will resize capacity if needed, but giving a higher start should improve performance
690}
691
692bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key, const char* value) {
693 if (ffStrEqualsIgnCase(key, "--stat")) {
694 if (ffOptionParseBoolean(value)) {
695 options->stat = 0;
696 options->showErrors = true;
697 } else if (value) {
698 char* end;
699 uint32_t num = (uint32_t) strtoul(value, &end, 10);
700 if (*end == '\0') {
701 options->stat = (int32_t) num;
702 options->showErrors = true;
703 } else {
704 options->stat = -1;
705 }
706 } else {
707 options->stat = -1;
708 }
709 } else if (ffStrEqualsIgnCase(key, "--pipe")) {
710 options->pipe = ffOptionParseBoolean(value);
711 } else if (ffStrEqualsIgnCase(key, "--show-errors")) {
712 options->showErrors = ffOptionParseBoolean(value);
713 } else if (ffStrEqualsIgnCase(key, "--debug"))
714#ifndef NDEBUG
715 options->debugMode = ffOptionParseBoolean(value);
716#else
717 {
718 fprintf(stderr, "--debug is only available in debug builds\n");
719 exit(477);
720 }
721#endif
722 else if (ffStrEqualsIgnCase(key, "--disable-linewrap")) {
723 options->disableLinewrap = ffOptionParseBoolean(value);
724 } else if (ffStrEqualsIgnCase(key, "--hide-cursor")) {
725 options->hideCursor = ffOptionParseBoolean(value);
726 } else if (ffStrEqualsIgnCase(key, "--separator")) {
727 ffOptionParseString(key, value, &options->keyValueSeparator);
728 } else if (ffStrEqualsIgnCase(key, "--color")) {
729 optionCheckString(key, value, &options->colorKeys);
730 ffOptionParseColor(value, &options->colorKeys);
731 ffStrbufSet(&options->colorTitle, &options->colorKeys);
732 } else if (ffStrStartsWithIgnCase(key, "--color-")) {
733 const char* subkey = key + strlen("--color-");
734 if (ffStrEqualsIgnCase(subkey, "keys")) {
735 optionCheckString(key, value, &options->colorKeys);
736 ffOptionParseColor(value, &options->colorKeys);
737 } else if (ffStrEqualsIgnCase(subkey, "title")) {
738 optionCheckString(key, value, &options->colorTitle);
739 ffOptionParseColor(value, &options->colorTitle);
740 } else if (ffStrEqualsIgnCase(subkey, "output")) {
741 optionCheckString(key, value, &options->colorOutput);
742 ffOptionParseColor(value, &options->colorOutput);
743 } else if (ffStrEqualsIgnCase(subkey, "separator")) {
744 optionCheckString(key, value, &options->colorSeparator);
745 ffOptionParseColor(value, &options->colorSeparator);
746 } else {
747 return false;
748 }
749 } else if (ffStrStartsWithIgnCase(key, "--key-")) {
750 const char* subkey = key + strlen("--key-");
751 if (ffStrEqualsIgnCase(subkey, "width")) {
752 options->keyWidth = (uint16_t) ffOptionParseUInt32(key, value);
753 } else if (ffStrEqualsIgnCase(subkey, "type")) {
754 options->keyType = (FFModuleKeyType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "none", FF_MODULE_KEY_TYPE_NONE }, { "string", FF_MODULE_KEY_TYPE_STRING }, { "icon", FF_MODULE_KEY_TYPE_ICON }, { "both", FF_MODULE_KEY_TYPE_BOTH }, { "both-0", FF_MODULE_KEY_TYPE_BOTH_0 }, { "both-1", FF_MODULE_KEY_TYPE_BOTH_1 }, { "both-2", FF_MODULE_KEY_TYPE_BOTH_2 }, { "both-3", FF_MODULE_KEY_TYPE_BOTH_3 }, { "both-4", FF_MODULE_KEY_TYPE_BOTH_4 }, {} });
755 } else if (ffStrEqualsIgnCase(subkey, "padding-left")) {
756 options->keyPaddingLeft = (uint16_t) ffOptionParseUInt32(key, value);
757 } else if (ffStrEqualsIgnCase(subkey, "language")) {
758 FF_STRBUF_AUTO_DESTROY language = ffStrbufCreateS(value);
759 options->keyLanguage = optionParseLanguageString(&language);
760 } else {
761 return false;
762 }
763 } else if (ffStrEqualsIgnCase(key, "--bright-color")) {
764 options->brightColor = ffOptionParseBoolean(value);
765 } else if (ffStrEqualsIgnCase(key, "--binary-prefix")) {
766 fprintf(stderr, "--binary-prefix has been renamed to --size-binary-prefix\n");
767 exit(477);
768 } else if (ffStrStartsWithIgnCase(key, "--duration-")) {
769 const char* subkey = key + strlen("--duration-");
770 if (ffStrEqualsIgnCase(subkey, "abbreviation")) {
771 options->durationAbbreviation = ffOptionParseBoolean(value);
772 } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) {
773 options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
774 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
775 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
776 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
777 {},
778 });
779 } else {
780 return false;
781 }
782 } else if (ffStrStartsWithIgnCase(key, "--size-")) {
783 const char* subkey = key + strlen("--size-");
784 if (ffStrEqualsIgnCase(subkey, "binary-prefix")) {
785 options->sizeBinaryPrefix = (FFSizeBinaryPrefixType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "iec", FF_SIZE_BINARY_PREFIX_TYPE_IEC }, { "si", FF_SIZE_BINARY_PREFIX_TYPE_SI }, { "jedec", FF_SIZE_BINARY_PREFIX_TYPE_JEDEC }, {} });
786 } else if (ffStrEqualsIgnCase(subkey, "ndigits")) {
787 uint32_t num = ffOptionParseUInt32(key, value);
788 if (num > 9) {
789 fprintf(stderr, "Error: %s must be between 0 and 9\n", key);
790 exit(479);
791 }
792 options->sizeNdigits = (uint8_t) num;
793 } else if (ffStrEqualsIgnCase(subkey, "max-prefix")) {
794 options->sizeMaxPrefix = (uint8_t) ffOptionParseEnum(
795 key,
796 value,
797 (FFKeyValuePair[]) {
798 { "B", 0 },
799 { "kB", 1 },
800 { "MB", 2 },
801 { "GB", 3 },
802 { "TB", 4 },
803 { "PB", 5 },
804 { "EB", 6 },
805 { "ZB", 7 },
806 { "YB", 8 },
807 {},
808 });
809 } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) {
810 options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
811 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
812 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
813 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
814 {},
815 });
816 } else {
817 return false;
818 }
819 } else if (ffStrStartsWithIgnCase(key, "--temp-")) {
820 const char* subkey = key + strlen("--temp-");
821 if (ffStrEqualsIgnCase(subkey, "unit")) {
822 options->tempUnit = (FFTemperatureUnit) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
823 { "DEFAULT", FF_TEMPERATURE_UNIT_DEFAULT },
824 { "D", FF_TEMPERATURE_UNIT_DEFAULT },
825 { "CELSIUS", FF_TEMPERATURE_UNIT_CELSIUS },
826 { "C", FF_TEMPERATURE_UNIT_CELSIUS },
827 { "FAHRENHEIT", FF_TEMPERATURE_UNIT_FAHRENHEIT },
828 { "F", FF_TEMPERATURE_UNIT_FAHRENHEIT },
829 { "KELVIN", FF_TEMPERATURE_UNIT_KELVIN },
830 { "K", FF_TEMPERATURE_UNIT_KELVIN },
831 {},
832 });
833 } else if (ffStrEqualsIgnCase(subkey, "ndigits")) {
834 options->tempNdigits = (uint8_t) ffOptionParseUInt32(key, value);
835 } else if (ffStrEqualsIgnCase(subkey, "color-green")) {
836 ffOptionParseColor(value, &options->tempColorGreen);
837 } else if (ffStrEqualsIgnCase(subkey, "color-yellow")) {
838 ffOptionParseColor(value, &options->tempColorYellow);
839 } else if (ffStrEqualsIgnCase(subkey, "color-red")) {
840 ffOptionParseColor(value, &options->tempColorRed);
841 } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) {
842 options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
843 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
844 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
845 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
846 {},
847 });
848 } else {
849 return false;
850 }
851 } else if (ffStrStartsWithIgnCase(key, "--percent-")) {
852 const char* subkey = key + strlen("--percent-");
853 if (ffStrEqualsIgnCase(subkey, "type")) {
854 options->percentType = (uint8_t) ffOptionParseUInt32(key, value);
855 } else if (ffStrEqualsIgnCase(subkey, "ndigits")) {
856 options->percentNdigits = (uint8_t) ffOptionParseUInt32(key, value);
857 } else if (ffStrEqualsIgnCase(subkey, "color-green")) {
858 ffOptionParseColor(value, &options->percentColorGreen);
859 } else if (ffStrEqualsIgnCase(subkey, "color-yellow")) {
860 ffOptionParseColor(value, &options->percentColorYellow);
861 } else if (ffStrEqualsIgnCase(subkey, "color-red")) {
862 ffOptionParseColor(value, &options->percentColorRed);
863 } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) {
864 options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
865 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
866 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
867 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
868 {},
869 });
870 } else if (ffStrEqualsIgnCase(subkey, "width")) {
871 options->percentWidth = (uint8_t) ffOptionParseUInt32(key, value);
872 } else {
873 return false;
874 }
875 } else if (ffStrEqualsIgnCase(key, "--fraction-ndigits")) {
876 options->fractionNdigits = (int8_t) ffOptionParseInt32(key, value);
877 } else if (ffStrEqualsIgnCase(key, "--fraction-trailing-zeros")) {
878 options->fractionTrailingZeros = (FFFractionTrailingZerosType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
879 { "default", FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT },
880 { "always", FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS },
881 { "never", FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER },
882 {},
883 });
884 } else if (ffStrEqualsIgnCase(key, "--no-buffer")) {
885 options->noBuffer = ffOptionParseBoolean(value);
886 } else if (ffStrStartsWithIgnCase(key, "--bar-")) {
887 const char* subkey = key + strlen("--bar-");
888 if (ffStrEqualsIgnCase(subkey, "char-elapsed")) {
889 ffOptionParseString(key, value, &options->barCharElapsed);
890 } else if (ffStrEqualsIgnCase(subkey, "char-total")) {
891 ffOptionParseString(key, value, &options->barCharTotal);
892 } else if (ffStrEqualsIgnCase(subkey, "width")) {
893 options->barWidth = (uint8_t) ffOptionParseUInt32(key, value);
894 } else if (ffStrEqualsIgnCase(subkey, "border-left")) {
895 ffOptionParseString(key, value, &options->barBorderLeft);
896 } else if (ffStrEqualsIgnCase(subkey, "border-right")) {
897 ffOptionParseString(key, value, &options->barBorderRight);
898 } else if (ffStrEqualsIgnCase(subkey, "border-left-elapsed")) {
899 ffOptionParseString(key, value, &options->barBorderLeftElapsed);
900 } else if (ffStrEqualsIgnCase(subkey, "border-right-elapsed")) {
901 ffOptionParseString(key, value, &options->barBorderRightElapsed);
902 } else if (ffStrEqualsIgnCase(subkey, "color-elapsed")) {
903 if (!value) {
904 ffStrbufClear(&options->barColorElapsed);
905 } else if (ffStrEqualsIgnCase(value, "auto")) {
906 ffStrbufSetStatic(&options->barColorElapsed, "auto");
907 } else {
908 ffOptionParseColor(value, &options->barColorElapsed);
909 }
910 } else if (ffStrEqualsIgnCase(subkey, "color-total")) {
911 ffOptionParseColor(value, &options->barColorTotal);
912 } else if (ffStrEqualsIgnCase(subkey, "color-border")) {
913 ffOptionParseColor(value, &options->barColorBorder);
914 } else {
915 return false;
916 }
917 } else if (ffStrStartsWithIgnCase(key, "--freq-")) {
918 const char* subkey = key + strlen("--freq-");
919 if (ffStrEqualsIgnCase(subkey, "ndigits")) {
920 options->freqNdigits = (int8_t) ffOptionParseInt32(key, value);
921 } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) {
922 options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
923 { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
924 { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
925 { "never", FF_SPACE_BEFORE_UNIT_NEVER },
926 {},
927 });
928 } else {
929 return false;
930 }
931 } else {
932 return false;
933 }
934 return true;
935}
936
937void ffOptionsInitDisplay(FFOptionsDisplay* options) {
938 bool terminalLightTheme = false;
939 #if !FF_MODULE_DISABLE_TERMINALTHEME
940 {
941 // don't enable bright color if the terminal is in light mode
942 FFTerminalThemeResult result;
943 if (ffDetectTerminalTheme(&result, true /* forceEnv for performance */) && !result.bg.dark) {
944 terminalLightTheme = true;
945 }
946 }
947 #endif
948
949 ffStrbufInit(&options->colorKeys);
950 ffStrbufInit(&options->colorTitle);
951 ffStrbufInit(&options->colorOutput);
952 ffStrbufInit(&options->colorSeparator);
953 options->brightColor = !terminalLightTheme;
954 ffStrbufInitStatic(&options->keyValueSeparator, ": ");
955
956 options->showErrors = false;
957 options->pipe = !ffIsTerminal(STDOUT_FILENO) || !!getenv("NO_COLOR");
958 options->disableLinewrap = false;
959
960#ifndef NDEBUG
961 options->debugMode = !!getenv("FF_DEBUG");
962#endif
963
964 options->durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
965 options->hideCursor = false;
966 options->sizeBinaryPrefix = FF_SIZE_BINARY_PREFIX_TYPE_IEC;
967 options->sizeNdigits = 2;
968 options->sizeMaxPrefix = 8; // YB
969 options->sizeSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
970
971 options->stat = -1;
972 options->noBuffer = false;
973 options->keyWidth = 0;
974 options->keyPaddingLeft = 0;
975 options->keyType = FF_MODULE_KEY_TYPE_STRING;
976 options->keyLanguage = offsetof(FFModuleDisplayName, en);
977
978 options->tempUnit = FF_TEMPERATURE_UNIT_DEFAULT;
979 options->tempNdigits = 1;
980 ffStrbufInitStatic(&options->tempColorGreen, FF_COLOR_FG_GREEN);
981 ffStrbufInitStatic(&options->tempColorYellow, terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW);
982 ffStrbufInitStatic(&options->tempColorRed, terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED);
983 options->tempSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
984
985 ffStrbufInitStatic(&options->barCharElapsed, "â– ");
986 ffStrbufInitStatic(&options->barCharTotal, "-");
987 ffStrbufInitStatic(&options->barBorderLeft, "[ ");
988 ffStrbufInitStatic(&options->barBorderRight, " ]");
989 ffStrbufInit(&options->barBorderLeftElapsed);
990 ffStrbufInit(&options->barBorderRightElapsed);
991 ffStrbufInitStatic(&options->barColorElapsed, "auto");
992 ffStrbufInitStatic(&options->barColorTotal, terminalLightTheme ? FF_COLOR_FG_WHITE : FF_COLOR_FG_LIGHT_WHITE);
993 ffStrbufInitStatic(&options->barColorBorder, terminalLightTheme ? FF_COLOR_FG_WHITE : FF_COLOR_FG_LIGHT_WHITE);
994 options->barWidth = 10;
995
996 options->durationAbbreviation = false;
997 options->durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
998 options->percentType = FF_PERCENTAGE_TYPE_NUM_BIT | FF_PERCENTAGE_TYPE_NUM_COLOR_BIT;
999 options->percentNdigits = 0;
1000 ffStrbufInitStatic(&options->percentColorGreen, FF_COLOR_FG_GREEN);
1001 ffStrbufInitStatic(&options->percentColorYellow, terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW);
1002 ffStrbufInitStatic(&options->percentColorRed, terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED);
1003 options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
1004 options->percentWidth = 0;
1005
1006 options->freqNdigits = 2;
1007 options->freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
1008 options->fractionNdigits = 2;
1009 options->fractionTrailingZeros = FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT;
1010
1011 ffListInit(&options->constants);
1012}
1013
1014void ffOptionsDestroyDisplay(FFOptionsDisplay* options) {
1015 ffStrbufDestroy(&options->colorKeys);
1016 ffStrbufDestroy(&options->colorTitle);
1017 ffStrbufDestroy(&options->colorOutput);
1018 ffStrbufDestroy(&options->colorSeparator);
1019 ffStrbufDestroy(&options->keyValueSeparator);
1020 ffStrbufDestroy(&options->barCharElapsed);
1021 ffStrbufDestroy(&options->barCharTotal);
1022 ffStrbufDestroy(&options->barBorderLeft);
1023 ffStrbufDestroy(&options->barBorderRight);
1024 ffStrbufDestroy(&options->barBorderLeftElapsed);
1025 ffStrbufDestroy(&options->barBorderRightElapsed);
1026 ffStrbufDestroy(&options->barColorElapsed);
1027 ffStrbufDestroy(&options->barColorTotal);
1028 ffStrbufDestroy(&options->barColorBorder);
1029 ffStrbufDestroy(&options->tempColorGreen);
1030 ffStrbufDestroy(&options->tempColorYellow);
1031 ffStrbufDestroy(&options->tempColorRed);
1032 ffStrbufDestroy(&options->percentColorGreen);
1033 ffStrbufDestroy(&options->percentColorYellow);
1034 ffStrbufDestroy(&options->percentColorRed);
1035 FF_LIST_FOR_EACH (FFstrbuf, item, options->constants) {
1036 ffStrbufDestroy(item);
1037 }
1038 ffListDestroy(&options->constants);
1039}
1040
1041// `names` is indexed by the enum value, so its order must match the enum declaration. A value outside
1042// the array adds no key at all, which is what the exhaustive switches this replaces did.
1043static void optionAddEnumString(yyjson_mut_doc* doc, yyjson_mut_val* obj, const char* key, const char* const names[], size_t count, uint32_t value) {
1044 if (value < count) {
1045 yyjson_mut_obj_add_str(doc, obj, key, names[value]);
1046 }
1047}
1048
1049static void optionAddSpaceBeforeUnit(yyjson_mut_doc* doc, yyjson_mut_val* obj, FFSpaceBeforeUnitType type) {
1050 static const char* const names[] = {"default", "always", "never"};
1051 optionAddEnumString(doc, obj, "spaceBeforeUnit", names, sizeof(names) / sizeof(*names), type);
1052}
1053
1054// @returns the language code for offsetof(FFModuleDisplayName, <language>), or nullptr if unknown
1055// @see optionParseLanguageString
1056static const char* optionGetLanguageString(uint32_t offset) {
1057 switch (offset) {
1058 case offsetof(FFModuleDisplayName, en): return "en";
1059 case offsetof(FFModuleDisplayName, ar): return "ar";
1060 case offsetof(FFModuleDisplayName, cs): return "cs";
1061 case offsetof(FFModuleDisplayName, de): return "de";
1062 case offsetof(FFModuleDisplayName, es): return "es";
1063 case offsetof(FFModuleDisplayName, fi): return "fi";
1064 case offsetof(FFModuleDisplayName, fr): return "fr";
1065 case offsetof(FFModuleDisplayName, gl): return "gl";
1066 case offsetof(FFModuleDisplayName, he): return "he";
1067 case offsetof(FFModuleDisplayName, id): return "id";
1068 case offsetof(FFModuleDisplayName, it): return "it";
1069 case offsetof(FFModuleDisplayName, ja): return "ja";
1070 case offsetof(FFModuleDisplayName, ko): return "ko";
1071 case offsetof(FFModuleDisplayName, pl): return "pl";
1072 case offsetof(FFModuleDisplayName, pt): return "pt";
1073 case offsetof(FFModuleDisplayName, ru): return "ru";
1074 case offsetof(FFModuleDisplayName, tr): return "tr";
1075 case offsetof(FFModuleDisplayName, uk): return "uk";
1076 case offsetof(FFModuleDisplayName, vi): return "vi";
1077 case offsetof(FFModuleDisplayName, zh_CN): return "zh_CN";
1078 case offsetof(FFModuleDisplayName, zh_TW): return "zh_TW";
1079 }
1080 return nullptr;
1081}
1082
1083[[gnu::cold]]
1084void ffOptionsGenerateDisplayJsonConfig(FFdata* data, FFOptionsDisplay* options) {
1085 yyjson_mut_doc* doc = data->resultDoc;
1086 yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, doc->root, "display");
1087
1088 if (options->stat <= 0) {
1089 yyjson_mut_obj_add_bool(doc, obj, "stat", options->stat == 0);
1090 } else {
1091 yyjson_mut_obj_add_int(doc, obj, "stat", options->stat);
1092 }
1093
1094 yyjson_mut_obj_add_bool(doc, obj, "pipe", options->pipe);
1095
1096 yyjson_mut_obj_add_bool(doc, obj, "showErrors", options->showErrors);
1097
1098 yyjson_mut_obj_add_bool(doc, obj, "disableLinewrap", options->disableLinewrap);
1099
1100 yyjson_mut_obj_add_bool(doc, obj, "hideCursor", options->hideCursor);
1101
1102 yyjson_mut_obj_add_strbuf(doc, obj, "separator", &options->keyValueSeparator);
1103
1104 {
1105 yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, obj, "color");
1106 yyjson_mut_obj_add_strbuf(doc, color, "keys", &options->colorKeys);
1107 yyjson_mut_obj_add_strbuf(doc, color, "title", &options->colorTitle);
1108 yyjson_mut_obj_add_strbuf(doc, color, "output", &options->colorOutput);
1109 yyjson_mut_obj_add_strbuf(doc, color, "separator", &options->colorSeparator);
1110 }
1111
1112 yyjson_mut_obj_add_bool(doc, obj, "brightColor", options->brightColor);
1113
1114 {
1115 yyjson_mut_val* duration = yyjson_mut_obj_add_obj(doc, obj, "duration");
1116 yyjson_mut_obj_add_bool(doc, duration, "abbreviation", options->durationAbbreviation);
1117 optionAddSpaceBeforeUnit(doc, duration, options->durationSpaceBeforeUnit);
1118 }
1119
1120 {
1121 static const char* const binaryPrefixNames[] = {"iec", "si", "jedec"};
1122 yyjson_mut_val* size = yyjson_mut_obj_add_obj(doc, obj, "size");
1123 yyjson_mut_obj_add_str(doc, size, "maxPrefix", ((const char*[]) {
1124 "B",
1125 "kB",
1126 "MB",
1127 "GB",
1128 "TB",
1129 "PB",
1130 "EB",
1131 "ZB",
1132 "YB",
1133 })[options->sizeMaxPrefix]);
1134 optionAddEnumString(doc, size, "binaryPrefix", binaryPrefixNames, sizeof(binaryPrefixNames) / sizeof(*binaryPrefixNames), options->sizeBinaryPrefix);
1135 yyjson_mut_obj_add_uint(doc, size, "ndigits", options->sizeNdigits);
1136 optionAddSpaceBeforeUnit(doc, size, options->sizeSpaceBeforeUnit);
1137 }
1138
1139 {
1140 static const char* const tempUnitNames[] = {"D", "C", "F", "K"};
1141 yyjson_mut_val* temperature = yyjson_mut_obj_add_obj(doc, obj, "temp");
1142 optionAddEnumString(doc, temperature, "unit", tempUnitNames, sizeof(tempUnitNames) / sizeof(*tempUnitNames), options->tempUnit);
1143 yyjson_mut_obj_add_uint(doc, temperature, "ndigits", options->tempNdigits);
1144 {
1145 yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, temperature, "color");
1146 yyjson_mut_obj_add_strbuf(doc, color, "green", &options->tempColorGreen);
1147 yyjson_mut_obj_add_strbuf(doc, color, "yellow", &options->tempColorYellow);
1148 yyjson_mut_obj_add_strbuf(doc, color, "red", &options->tempColorRed);
1149 }
1150 optionAddSpaceBeforeUnit(doc, temperature, options->tempSpaceBeforeUnit);
1151 }
1152
1153 {
1154 yyjson_mut_val* percent = yyjson_mut_obj_add_obj(doc, obj, "percent");
1155 {
1156 yyjson_mut_val* type = yyjson_mut_obj_add_arr(doc, percent, "type");
1157 if (options->percentType & FF_PERCENTAGE_TYPE_NUM_BIT) {
1158 yyjson_mut_arr_add_str(doc, type, "num");
1159 }
1160 if (options->percentType & FF_PERCENTAGE_TYPE_BAR_BIT) {
1161 yyjson_mut_arr_add_str(doc, type, "bar");
1162 }
1163 if (options->percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT) {
1164 yyjson_mut_arr_add_str(doc, type, "hide-others");
1165 }
1166 if (options->percentType & FF_PERCENTAGE_TYPE_NUM_COLOR_BIT) {
1167 yyjson_mut_arr_add_str(doc, type, "num-color");
1168 }
1169 if (options->percentType & FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT) {
1170 yyjson_mut_arr_add_str(doc, type, "bar-monochrome");
1171 }
1172 }
1173 yyjson_mut_obj_add_uint(doc, percent, "ndigits", options->percentNdigits);
1174 {
1175 yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, percent, "color");
1176 yyjson_mut_obj_add_strbuf(doc, color, "green", &options->percentColorGreen);
1177 yyjson_mut_obj_add_strbuf(doc, color, "yellow", &options->percentColorYellow);
1178 yyjson_mut_obj_add_strbuf(doc, color, "red", &options->percentColorRed);
1179 }
1180 optionAddSpaceBeforeUnit(doc, percent, options->percentSpaceBeforeUnit);
1181 yyjson_mut_obj_add_uint(doc, percent, "width", options->percentWidth);
1182 }
1183
1184 {
1185 yyjson_mut_val* bar = yyjson_mut_obj_add_obj(doc, obj, "bar");
1186
1187 yyjson_mut_val* char_ = yyjson_mut_obj_add_obj(doc, bar, "char");
1188 yyjson_mut_obj_add_strbuf(doc, char_, "elapsed", &options->barCharElapsed);
1189 yyjson_mut_obj_add_strbuf(doc, char_, "total", &options->barCharTotal);
1190
1191 yyjson_mut_val* border = yyjson_mut_obj_add_obj(doc, bar, "border");
1192 yyjson_mut_obj_add_strbuf(doc, border, "left", &options->barBorderLeft);
1193 yyjson_mut_obj_add_strbuf(doc, border, "right", &options->barBorderRight);
1194 yyjson_mut_obj_add_strbuf(doc, border, "leftElapsed", &options->barBorderLeftElapsed);
1195 yyjson_mut_obj_add_strbuf(doc, border, "rightElapsed", &options->barBorderRightElapsed);
1196
1197 yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, bar, "color");
1198 yyjson_mut_obj_add_strbuf(doc, color, "elapsed", &options->barColorElapsed);
1199 yyjson_mut_obj_add_strbuf(doc, color, "total", &options->barColorTotal);
1200 yyjson_mut_obj_add_strbuf(doc, color, "border", &options->barColorBorder);
1201
1202 yyjson_mut_obj_add_uint(doc, bar, "width", options->barWidth);
1203 }
1204
1205 {
1206 static const char* const trailingZerosNames[] = {"default", "always", "never"};
1207 yyjson_mut_val* fraction = yyjson_mut_obj_add_obj(doc, obj, "fraction");
1208
1209 if (options->fractionNdigits < 0) {
1210 yyjson_mut_obj_add_null(doc, fraction, "ndigits");
1211 } else {
1212 yyjson_mut_obj_add_uint(doc, fraction, "ndigits", (uint8_t) options->fractionNdigits);
1213 }
1214
1215 optionAddEnumString(doc, fraction, "trailingZeros", trailingZerosNames, sizeof(trailingZerosNames) / sizeof(*trailingZerosNames), options->fractionTrailingZeros);
1216 }
1217
1218 yyjson_mut_obj_add_bool(doc, obj, "noBuffer", options->noBuffer);
1219
1220 {
1221 yyjson_mut_val* key = yyjson_mut_obj_add_obj(doc, obj, "key");
1222 yyjson_mut_obj_add_uint(doc, key, "width", options->keyWidth);
1223 switch ((uint8_t) options->keyType) {
1224 case FF_MODULE_KEY_TYPE_NONE:
1225 yyjson_mut_obj_add_str(doc, key, "type", "none");
1226 break;
1227 case FF_MODULE_KEY_TYPE_STRING:
1228 yyjson_mut_obj_add_str(doc, key, "type", "string");
1229 break;
1230 case FF_MODULE_KEY_TYPE_ICON:
1231 yyjson_mut_obj_add_str(doc, key, "type", "icon");
1232 break;
1233 case FF_MODULE_KEY_TYPE_BOTH_0:
1234 yyjson_mut_obj_add_str(doc, key, "type", "both-0");
1235 break;
1236 case FF_MODULE_KEY_TYPE_BOTH_1: // alias: both
1237 yyjson_mut_obj_add_str(doc, key, "type", "both");
1238 break;
1239 case FF_MODULE_KEY_TYPE_BOTH_2:
1240 yyjson_mut_obj_add_str(doc, key, "type", "both-2");
1241 break;
1242 case FF_MODULE_KEY_TYPE_BOTH_3:
1243 yyjson_mut_obj_add_str(doc, key, "type", "both-3");
1244 break;
1245 case FF_MODULE_KEY_TYPE_BOTH_4:
1246 yyjson_mut_obj_add_str(doc, key, "type", "both-4");
1247 break;
1248 }
1249
1250 yyjson_mut_obj_add_uint(doc, key, "paddingLeft", options->keyPaddingLeft);
1251
1252 const char* language = optionGetLanguageString(options->keyLanguage);
1253 if (language != nullptr) {
1254 yyjson_mut_obj_add_str(doc, key, "language", language);
1255 }
1256 }
1257
1258 {
1259 yyjson_mut_val* freq = yyjson_mut_obj_add_obj(doc, obj, "freq");
1260 yyjson_mut_obj_add_int(doc, freq, "ndigits", options->freqNdigits);
1261 optionAddSpaceBeforeUnit(doc, freq, options->freqSpaceBeforeUnit);
1262 }
1263
1264 {
1265 yyjson_mut_val* constants = yyjson_mut_obj_add_arr(doc, obj, "constants");
1266 FF_LIST_FOR_EACH (FFstrbuf, item, options->constants) {
1267 yyjson_mut_arr_add_strbuf(doc, constants, item);
1268 }
1269 }
1270}