summaryrefslogtreecommitdiffstats
path: root/src/options
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/options
Add the files
Diffstat (limited to 'src/options')
-rw-r--r--src/options/display.c1168
-rw-r--r--src/options/display.h97
-rw-r--r--src/options/general.c134
-rw-r--r--src/options/general.h27
-rw-r--r--src/options/logo.c562
-rw-r--r--src/options/logo.h61
6 files changed, 2049 insertions, 0 deletions
diff --git a/src/options/display.c b/src/options/display.c
new file mode 100644
index 0000000..ec80a37
--- /dev/null
+++ b/src/options/display.c
@@ -0,0 +1,1168 @@
+#include "fastfetch.h"
+#include "common/color.h"
+#include "common/jsonconfig.h"
+#include "common/percent.h"
+#include "common/strutil.h"
+#include "options/display.h"
+
+#include <unistd.h>
+
+const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_val* root) {
+ yyjson_val* object = yyjson_obj_get(root, "display");
+ if (!object) {
+ return NULL;
+ }
+ if (!yyjson_is_obj(object)) {
+ return "Property 'display' must be an object";
+ }
+
+ yyjson_val *key, *val;
+ size_t idx, max;
+ yyjson_obj_foreach (object, idx, max, key, val) {
+ if (unsafe_yyjson_equals_str(key, "stat")) {
+ if (yyjson_is_bool(val)) {
+ if (yyjson_get_bool(val)) {
+ options->stat = 0;
+ options->showErrors = true;
+ } else {
+ options->stat = -1;
+ }
+ } else if (yyjson_is_uint(val)) {
+ options->stat = (int) yyjson_get_uint(val);
+ options->showErrors = true;
+ } else {
+ return "display.stat must be a boolean or a positive integer";
+ }
+ } else if (unsafe_yyjson_equals_str(key, "pipe")) {
+ options->pipe = yyjson_get_bool(val);
+ } else if (unsafe_yyjson_equals_str(key, "showErrors")) {
+ options->showErrors = yyjson_get_bool(val);
+ } else if (unsafe_yyjson_equals_str(key, "disableLinewrap")) {
+ options->disableLinewrap = yyjson_get_bool(val);
+ } else if (unsafe_yyjson_equals_str(key, "hideCursor")) {
+ options->hideCursor = yyjson_get_bool(val);
+ } else if (unsafe_yyjson_equals_str(key, "separator")) {
+ ffStrbufSetJsonVal(&options->keyValueSeparator, val);
+ } else if (unsafe_yyjson_equals_str(key, "color")) {
+ if (yyjson_is_str(val)) {
+ ffOptionParseColor(unsafe_yyjson_get_str(val), &options->colorKeys);
+ ffStrbufSet(&options->colorTitle, &options->colorKeys);
+ } else if (yyjson_is_obj(val)) {
+ yyjson_val* colorKeys = yyjson_obj_get(val, "keys");
+ if (colorKeys) {
+ ffOptionParseColor(yyjson_get_str(colorKeys), &options->colorKeys);
+ }
+ yyjson_val* colorTitle = yyjson_obj_get(val, "title");
+ if (colorTitle) {
+ ffOptionParseColor(yyjson_get_str(colorTitle), &options->colorTitle);
+ }
+ yyjson_val* colorOutput = yyjson_obj_get(val, "output");
+ if (colorOutput) {
+ ffOptionParseColor(yyjson_get_str(colorOutput), &options->colorOutput);
+ }
+ yyjson_val* colorSeparator = yyjson_obj_get(val, "separator");
+ if (colorSeparator) {
+ ffOptionParseColor(yyjson_get_str(colorSeparator), &options->colorSeparator);
+ }
+ } else {
+ return "display.color must be either a string or an object";
+ }
+ } else if (unsafe_yyjson_equals_str(key, "brightColor")) {
+ options->brightColor = yyjson_get_bool(val);
+ } else if (unsafe_yyjson_equals_str(key, "duration")) {
+ if (!yyjson_is_obj(val)) {
+ return "display.duration must be an object";
+ }
+
+ yyjson_val* abbreviation = yyjson_obj_get(val, "abbreviation");
+ if (abbreviation) {
+ options->durationAbbreviation = yyjson_get_bool(abbreviation);
+ }
+
+ yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
+ if (spaceBeforeUnit) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ if (error) {
+ return error;
+ }
+ options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
+ }
+ } else if (unsafe_yyjson_equals_str(key, "size")) {
+ if (!yyjson_is_obj(val)) {
+ return "display.size must be an object";
+ }
+
+ yyjson_val* maxPrefix = yyjson_obj_get(val, "maxPrefix");
+ if (maxPrefix) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(
+ maxPrefix,
+ &value,
+ (FFKeyValuePair[]) {
+ { "B", 0 },
+ { "kB", 1 },
+ { "MB", 2 },
+ { "GB", 3 },
+ { "TB", 4 },
+ { "PB", 5 },
+ { "EB", 6 },
+ { "ZB", 7 },
+ { "YB", 8 },
+ {},
+ });
+ if (error) {
+ return error;
+ }
+ options->sizeMaxPrefix = (uint8_t) value;
+ }
+
+ yyjson_val* binaryPrefix = yyjson_obj_get(val, "binaryPrefix");
+ if (binaryPrefix) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(binaryPrefix, &value, (FFKeyValuePair[]) {
+ { "iec", FF_SIZE_BINARY_PREFIX_TYPE_IEC },
+ { "si", FF_SIZE_BINARY_PREFIX_TYPE_SI },
+ { "jedec", FF_SIZE_BINARY_PREFIX_TYPE_JEDEC },
+ {},
+ });
+ if (error) {
+ return error;
+ }
+ options->sizeBinaryPrefix = (FFSizeBinaryPrefixType) value;
+ }
+
+ yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
+ if (ndigits) {
+ if (!yyjson_is_uint(ndigits)) {
+ return "display.size.ndigits must be an unsigned integer";
+ }
+ uint64_t val = yyjson_get_uint(ndigits);
+ if (val > 9) {
+ return "display.size.ndigits must be between 0 and 9";
+ }
+ options->sizeNdigits = (uint8_t) val;
+ }
+
+ yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
+ if (spaceBeforeUnit) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ if (error) {
+ return error;
+ }
+ options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
+ }
+ } else if (unsafe_yyjson_equals_str(key, "temp")) {
+ if (!yyjson_is_obj(val)) {
+ return "display.temp must be an object";
+ }
+
+ yyjson_val* unit = yyjson_obj_get(val, "unit");
+ if (unit) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(unit, &value, (FFKeyValuePair[]) {
+ { "DEFAULT", FF_TEMPERATURE_UNIT_DEFAULT },
+ { "D", FF_TEMPERATURE_UNIT_DEFAULT },
+ { "CELSIUS", FF_TEMPERATURE_UNIT_CELSIUS },
+ { "C", FF_TEMPERATURE_UNIT_CELSIUS },
+ { "FAHRENHEIT", FF_TEMPERATURE_UNIT_FAHRENHEIT },
+ { "F", FF_TEMPERATURE_UNIT_FAHRENHEIT },
+ { "KELVIN", FF_TEMPERATURE_UNIT_KELVIN },
+ { "K", FF_TEMPERATURE_UNIT_KELVIN },
+ {},
+ });
+ if (error) {
+ return error;
+ }
+ options->tempUnit = (FFTemperatureUnit) value;
+ }
+
+ yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
+ if (ndigits) {
+ if (!yyjson_is_uint(ndigits)) {
+ return "display.temperature.ndigits must be an unsigned integer";
+ }
+ uint64_t val = yyjson_get_uint(ndigits);
+ if (val > 9) {
+ return "display.temperature.ndigits must be between 0 and 9";
+ }
+ options->tempNdigits = (uint8_t) val;
+ }
+
+ yyjson_val* color = yyjson_obj_get(val, "color");
+ if (color) {
+ if (!yyjson_is_obj(color)) {
+ return "display.temperature.color must be an object";
+ }
+
+ yyjson_val* green = yyjson_obj_get(color, "green");
+ if (green) {
+ ffOptionParseColor(yyjson_get_str(green), &options->tempColorGreen);
+ }
+
+ yyjson_val* yellow = yyjson_obj_get(color, "yellow");
+ if (yellow) {
+ ffOptionParseColor(yyjson_get_str(yellow), &options->tempColorYellow);
+ }
+
+ yyjson_val* red = yyjson_obj_get(color, "red");
+ if (red) {
+ ffOptionParseColor(yyjson_get_str(red), &options->tempColorRed);
+ }
+ }
+
+ yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
+ if (spaceBeforeUnit) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ if (error) {
+ return error;
+ }
+ options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
+ }
+ } else if (unsafe_yyjson_equals_str(key, "percent")) {
+ if (!yyjson_is_obj(val)) {
+ return "display.percent must be an object";
+ }
+
+ yyjson_val* type = yyjson_obj_get(val, "type");
+ if (type) {
+ const char* error = ffPercentParseTypeJsonConfig(type, &options->percentType);
+ if (error) {
+ return error;
+ }
+ }
+
+ yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
+ if (ndigits) {
+ if (!yyjson_is_uint(ndigits)) {
+ return "display.percent.ndigits must be an unsigned integer";
+ }
+ uint64_t val = yyjson_get_uint(ndigits);
+ if (val > 9) {
+ return "display.percent.ndigits must be between 0 and 9";
+ }
+ options->percentNdigits = (uint8_t) val;
+ }
+
+ yyjson_val* color = yyjson_obj_get(val, "color");
+ if (color) {
+ if (!yyjson_is_obj(color)) {
+ return "display.percent.color must be an object";
+ }
+
+ yyjson_val* green = yyjson_obj_get(color, "green");
+ if (green) {
+ ffOptionParseColor(yyjson_get_str(green), &options->percentColorGreen);
+ }
+
+ yyjson_val* yellow = yyjson_obj_get(color, "yellow");
+ if (yellow) {
+ ffOptionParseColor(yyjson_get_str(yellow), &options->percentColorYellow);
+ }
+
+ yyjson_val* red = yyjson_obj_get(color, "red");
+ if (red) {
+ ffOptionParseColor(yyjson_get_str(red), &options->percentColorRed);
+ }
+ }
+
+ yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
+ if (spaceBeforeUnit) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ if (error) {
+ return error;
+ }
+ options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
+ }
+
+ yyjson_val* width = yyjson_obj_get(val, "width");
+ if (width) {
+ options->percentWidth = (uint8_t) yyjson_get_uint(width);
+ }
+ } else if (unsafe_yyjson_equals_str(key, "bar")) {
+ if (yyjson_is_obj(val)) {
+ yyjson_val* char_ = yyjson_obj_get(val, "char");
+ if (char_) {
+ if (!yyjson_is_obj(char_)) {
+ return "display.bar.char must be an object";
+ }
+
+ yyjson_val* charElapsed = yyjson_obj_get(char_, "elapsed");
+ if (charElapsed) {
+ ffStrbufSetJsonVal(&options->barCharElapsed, charElapsed);
+ }
+
+ yyjson_val* charTotal = yyjson_obj_get(char_, "total");
+ if (charTotal) {
+ ffStrbufSetJsonVal(&options->barCharTotal, charTotal);
+ }
+ } else {
+ yyjson_val* charElapsed = yyjson_obj_get(val, "charElapsed");
+ if (charElapsed) {
+ return "display.bar.charElapsed has been renamed to display.bar.char.elapsed.";
+ }
+
+ yyjson_val* charTotal = yyjson_obj_get(val, "charTotal");
+ if (charTotal) {
+ return "display.bar.charTotal has been renamed to display.bar.char.total.";
+ }
+ }
+
+ yyjson_val* border = yyjson_obj_get(val, "border");
+ if (border) {
+ if (yyjson_is_null(border)) {
+ ffStrbufClear(&options->barBorderLeft);
+ ffStrbufClear(&options->barBorderRight);
+ ffStrbufClear(&options->barBorderLeftElapsed);
+ ffStrbufClear(&options->barBorderRightElapsed);
+ } else {
+ if (!yyjson_is_obj(border)) {
+ return "display.bar.border must be an object";
+ }
+
+ yyjson_val* borderLeft = yyjson_obj_get(border, "left");
+ if (borderLeft) {
+ ffStrbufSetJsonVal(&options->barBorderLeft, borderLeft);
+ }
+
+ yyjson_val* borderRight = yyjson_obj_get(border, "right");
+ if (borderRight) {
+ ffStrbufSetJsonVal(&options->barBorderRight, borderRight);
+ }
+
+ yyjson_val* borderLeftElapsed = yyjson_obj_get(border, "leftElapsed");
+ if (borderLeftElapsed) {
+ ffStrbufSetJsonVal(&options->barBorderLeftElapsed, borderLeftElapsed);
+ }
+
+ yyjson_val* borderRightElapsed = yyjson_obj_get(border, "rightElapsed");
+ if (borderRightElapsed) {
+ ffStrbufSetJsonVal(&options->barBorderRightElapsed, borderRightElapsed);
+ }
+ }
+ } else {
+ yyjson_val* borderLeft = yyjson_obj_get(val, "borderLeft");
+ if (borderLeft) {
+ return "display.bar.borderLeft has been renamed to display.bar.border.left.";
+ }
+
+ yyjson_val* borderRight = yyjson_obj_get(val, "borderRight");
+ if (borderRight) {
+ return "display.bar.borderRight has been renamed to display.bar.border.right.";
+ }
+ }
+
+ yyjson_val* color = yyjson_obj_get(val, "color");
+ if (color) {
+ if (yyjson_is_null(color)) {
+ ffStrbufClear(&options->barColorElapsed);
+ ffStrbufClear(&options->barColorTotal);
+ ffStrbufClear(&options->barColorBorder);
+ } else {
+ if (!yyjson_is_obj(color)) {
+ return "display.bar.color must be an object";
+ }
+
+ yyjson_val* colorElapsed = yyjson_obj_get(color, "elapsed");
+ if (colorElapsed) {
+ const char* value = yyjson_get_str(colorElapsed);
+ if (!value) {
+ ffStrbufClear(&options->barColorElapsed);
+ } else if (ffStrEqualsIgnCase(value, "auto")) {
+ ffStrbufSetStatic(&options->barColorElapsed, "auto");
+ } else {
+ ffOptionParseColor(value, &options->barColorElapsed);
+ }
+ }
+
+ yyjson_val* colorTotal = yyjson_obj_get(color, "total");
+ if (colorTotal) {
+ ffOptionParseColor(yyjson_get_str(colorTotal), &options->barColorTotal);
+ }
+
+ yyjson_val* colorBorder = yyjson_obj_get(color, "border");
+ if (colorBorder) {
+ ffOptionParseColor(yyjson_get_str(colorBorder), &options->barColorBorder);
+ }
+ }
+ }
+
+ yyjson_val* width = yyjson_obj_get(val, "width");
+ if (width) {
+ options->barWidth = (uint8_t) yyjson_get_uint(width);
+ }
+ } else {
+ return "display.bar must be an object";
+ }
+ } else if (unsafe_yyjson_equals_str(key, "fraction")) {
+ if (yyjson_is_obj(val)) {
+ yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
+ if (ndigits) {
+ if (yyjson_is_null(ndigits)) {
+ options->fractionNdigits = -1;
+ } else {
+ if (!yyjson_is_int(ndigits)) {
+ return "display.fraction.ndigits must be an integer";
+ }
+ int64_t val = yyjson_get_int(ndigits);
+ if (val < -1 || val > 9) {
+ return "display.fraction.ndigits must be between -1 and 9";
+ }
+ options->fractionNdigits = (int8_t) val;
+ }
+ }
+ yyjson_val* trailingZeros = yyjson_obj_get(val, "trailingZeros");
+ if (trailingZeros) {
+ if (yyjson_is_null(trailingZeros)) {
+ options->fractionTrailingZeros = FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT;
+ } else {
+ int value;
+ const char* error = ffJsonConfigParseEnum(trailingZeros, &value, (FFKeyValuePair[]) {
+ { "default", FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT },
+ { "always", FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS },
+ { "never", FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER },
+ {},
+ });
+ if (error) {
+ return error;
+ }
+ options->fractionTrailingZeros = (FFFractionTrailingZerosType) value;
+ }
+ }
+ } else {
+ return "display.fraction must be an object";
+ }
+ } else if (unsafe_yyjson_equals_str(key, "noBuffer")) {
+ options->noBuffer = yyjson_get_bool(val);
+ } else if (unsafe_yyjson_equals_str(key, "key")) {
+ if (yyjson_is_obj(val)) {
+ yyjson_val* width = yyjson_obj_get(val, "width");
+ if (width) {
+ options->keyWidth = (uint16_t) yyjson_get_uint(width);
+ }
+
+ yyjson_val* type = yyjson_obj_get(val, "type");
+ if (type) {
+ int value;
+ 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 }, {} });
+ if (error) {
+ return error;
+ }
+ options->keyType = (uint8_t) value;
+ }
+
+ yyjson_val* paddingLeft = yyjson_obj_get(val, "paddingLeft");
+ if (paddingLeft) {
+ options->keyPaddingLeft = (uint16_t) yyjson_get_uint(paddingLeft);
+ }
+ } else {
+ return "display.key must be an object";
+ }
+ } else if (unsafe_yyjson_equals_str(key, "constants")) {
+ if (!yyjson_is_arr(val)) {
+ return "display.constants must be an array";
+ }
+ yyjson_val* item;
+ size_t idx, max;
+ yyjson_arr_foreach (val, idx, max, item) {
+ FFstrbuf* buffer = FF_LIST_ADD(FFstrbuf, options->constants);
+ ffStrbufInitJsonVal(buffer, item);
+ }
+ } else if (unsafe_yyjson_equals_str(key, "freq")) {
+ if (!yyjson_is_obj(val)) {
+ return "display.freq must be an object";
+ }
+
+ yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
+ if (ndigits) {
+ if (yyjson_is_null(ndigits)) {
+ options->freqNdigits = -1;
+ } else {
+ if (!yyjson_is_int(ndigits)) {
+ return "display.freq.ndigits must be an integer";
+ }
+ int64_t val = yyjson_get_int(ndigits);
+ if (val < -1 || val > 9) {
+ return "display.freq.ndigits must be between -1 and 9";
+ }
+ options->freqNdigits = (int8_t) val;
+ }
+ }
+
+ yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
+ if (spaceBeforeUnit) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ if (error) {
+ return error;
+ }
+ options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
+ }
+ } else if (unsafe_yyjson_equals_str(key, "common")) {
+ if (!yyjson_is_obj(val)) {
+ return "display.common must be an object";
+ }
+
+ yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
+ if (ndigits) {
+ if (!yyjson_is_uint(ndigits)) {
+ return "display.common.ndigits must be an unsigned integer";
+ }
+ uint64_t val = yyjson_get_uint(ndigits);
+ if (val > 9) {
+ return "display.common.ndigits must be between 0 and 9";
+ }
+ options->fractionNdigits = (int8_t) val;
+ options->freqNdigits = (int8_t) val;
+ options->percentNdigits = (uint8_t) val;
+ options->sizeNdigits = (uint8_t) val;
+ options->tempNdigits = (uint8_t) val;
+ }
+
+ yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit");
+ if (spaceBeforeUnit) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ if (error) {
+ return error;
+ }
+ options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
+ options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
+ options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
+ options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
+ options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) value;
+ }
+ } else {
+ return "Unknown display property";
+ }
+ }
+
+ return NULL;
+}
+
+static inline void optionCheckString(const char* key, const char* value, FFstrbuf* buffer) {
+ if (value == NULL) {
+ fprintf(stderr, "Error: usage: %s <str>\n", key);
+ exit(477);
+ }
+ ffStrbufEnsureFree(buffer, 63); // This is not needed, as ffStrbufSetS will resize capacity if needed, but giving a higher start should improve performance
+}
+
+bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key, const char* value) {
+ if (ffStrEqualsIgnCase(key, "--stat")) {
+ if (ffOptionParseBoolean(value)) {
+ options->stat = 0;
+ options->showErrors = true;
+ } else if (value) {
+ char* end;
+ uint32_t num = (uint32_t) strtoul(value, &end, 10);
+ if (*end == '\0') {
+ options->stat = (int32_t) num;
+ options->showErrors = true;
+ } else {
+ options->stat = -1;
+ }
+ } else {
+ options->stat = -1;
+ }
+ } else if (ffStrEqualsIgnCase(key, "--pipe")) {
+ options->pipe = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(key, "--show-errors")) {
+ options->showErrors = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(key, "--debug"))
+#ifndef NDEBUG
+ options->debugMode = ffOptionParseBoolean(value);
+#else
+ {
+ fprintf(stderr, "--debug is only available in debug builds\n");
+ exit(477);
+ }
+#endif
+ else if (ffStrEqualsIgnCase(key, "--disable-linewrap")) {
+ options->disableLinewrap = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(key, "--hide-cursor")) {
+ options->hideCursor = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(key, "--separator")) {
+ ffOptionParseString(key, value, &options->keyValueSeparator);
+ } else if (ffStrEqualsIgnCase(key, "--color")) {
+ optionCheckString(key, value, &options->colorKeys);
+ ffOptionParseColor(value, &options->colorKeys);
+ ffStrbufSet(&options->colorTitle, &options->colorKeys);
+ } else if (ffStrStartsWithIgnCase(key, "--color-")) {
+ const char* subkey = key + strlen("--color-");
+ if (ffStrEqualsIgnCase(subkey, "keys")) {
+ optionCheckString(key, value, &options->colorKeys);
+ ffOptionParseColor(value, &options->colorKeys);
+ } else if (ffStrEqualsIgnCase(subkey, "title")) {
+ optionCheckString(key, value, &options->colorTitle);
+ ffOptionParseColor(value, &options->colorTitle);
+ } else if (ffStrEqualsIgnCase(subkey, "output")) {
+ optionCheckString(key, value, &options->colorOutput);
+ ffOptionParseColor(value, &options->colorOutput);
+ } else if (ffStrEqualsIgnCase(subkey, "separator")) {
+ optionCheckString(key, value, &options->colorSeparator);
+ ffOptionParseColor(value, &options->colorSeparator);
+ } else {
+ return false;
+ }
+ } else if (ffStrStartsWithIgnCase(key, "--key-")) {
+ const char* subkey = key + strlen("--key-");
+ if (ffStrEqualsIgnCase(subkey, "width")) {
+ options->keyWidth = (uint16_t) ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subkey, "type")) {
+ 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 }, {} });
+ } else if (ffStrEqualsIgnCase(subkey, "padding-left")) {
+ options->keyPaddingLeft = (uint16_t) ffOptionParseUInt32(key, value);
+ } else {
+ return false;
+ }
+ } else if (ffStrEqualsIgnCase(key, "--bright-color")) {
+ options->brightColor = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(key, "--binary-prefix")) {
+ fprintf(stderr, "--binary-prefix has been renamed to --size-binary-prefix\n");
+ exit(477);
+ } else if (ffStrStartsWithIgnCase(key, "--duration-")) {
+ const char* subkey = key + strlen("--duration-");
+ if (ffStrEqualsIgnCase(subkey, "abbreviation")) {
+ options->durationAbbreviation = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) {
+ options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ } else {
+ return false;
+ }
+ } else if (ffStrStartsWithIgnCase(key, "--size-")) {
+ const char* subkey = key + strlen("--size-");
+ if (ffStrEqualsIgnCase(subkey, "binary-prefix")) {
+ 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 }, {} });
+ } else if (ffStrEqualsIgnCase(subkey, "ndigits")) {
+ uint32_t num = ffOptionParseUInt32(key, value);
+ if (num > 9) {
+ fprintf(stderr, "Error: %s must be between 0 and 9\n", key);
+ exit(479);
+ }
+ options->sizeNdigits = (uint8_t) num;
+ } else if (ffStrEqualsIgnCase(subkey, "max-prefix")) {
+ options->sizeMaxPrefix = (uint8_t) ffOptionParseEnum(
+ key,
+ value,
+ (FFKeyValuePair[]) {
+ { "B", 0 },
+ { "kB", 1 },
+ { "MB", 2 },
+ { "GB", 3 },
+ { "TB", 4 },
+ { "PB", 5 },
+ { "EB", 6 },
+ { "ZB", 7 },
+ { "YB", 8 },
+ {},
+ });
+ } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) {
+ options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ } else {
+ return false;
+ }
+ } else if (ffStrStartsWithIgnCase(key, "--temp-")) {
+ const char* subkey = key + strlen("--temp-");
+ if (ffStrEqualsIgnCase(subkey, "unit")) {
+ options->tempUnit = (FFTemperatureUnit) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "DEFAULT", FF_TEMPERATURE_UNIT_DEFAULT },
+ { "D", FF_TEMPERATURE_UNIT_DEFAULT },
+ { "CELSIUS", FF_TEMPERATURE_UNIT_CELSIUS },
+ { "C", FF_TEMPERATURE_UNIT_CELSIUS },
+ { "FAHRENHEIT", FF_TEMPERATURE_UNIT_FAHRENHEIT },
+ { "F", FF_TEMPERATURE_UNIT_FAHRENHEIT },
+ { "KELVIN", FF_TEMPERATURE_UNIT_KELVIN },
+ { "K", FF_TEMPERATURE_UNIT_KELVIN },
+ {},
+ });
+ } else if (ffStrEqualsIgnCase(subkey, "ndigits")) {
+ options->tempNdigits = (uint8_t) ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subkey, "color-green")) {
+ ffOptionParseColor(value, &options->tempColorGreen);
+ } else if (ffStrEqualsIgnCase(subkey, "color-yellow")) {
+ ffOptionParseColor(value, &options->tempColorYellow);
+ } else if (ffStrEqualsIgnCase(subkey, "color-red")) {
+ ffOptionParseColor(value, &options->tempColorRed);
+ } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) {
+ options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ } else {
+ return false;
+ }
+ } else if (ffStrStartsWithIgnCase(key, "--percent-")) {
+ const char* subkey = key + strlen("--percent-");
+ if (ffStrEqualsIgnCase(subkey, "type")) {
+ options->percentType = (uint8_t) ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subkey, "ndigits")) {
+ options->percentNdigits = (uint8_t) ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subkey, "color-green")) {
+ ffOptionParseColor(value, &options->percentColorGreen);
+ } else if (ffStrEqualsIgnCase(subkey, "color-yellow")) {
+ ffOptionParseColor(value, &options->percentColorYellow);
+ } else if (ffStrEqualsIgnCase(subkey, "color-red")) {
+ ffOptionParseColor(value, &options->percentColorRed);
+ } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) {
+ options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ } else if (ffStrEqualsIgnCase(subkey, "width")) {
+ options->percentWidth = (uint8_t) ffOptionParseUInt32(key, value);
+ } else {
+ return false;
+ }
+ } else if (ffStrEqualsIgnCase(key, "--fraction-ndigits")) {
+ options->fractionNdigits = (int8_t) ffOptionParseInt32(key, value);
+ } else if (ffStrEqualsIgnCase(key, "--fraction-trailing-zeros")) {
+ options->fractionTrailingZeros = (FFFractionTrailingZerosType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "default", FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT },
+ { "always", FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS },
+ { "never", FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER },
+ {},
+ });
+ } else if (ffStrEqualsIgnCase(key, "--no-buffer")) {
+ options->noBuffer = ffOptionParseBoolean(value);
+ } else if (ffStrStartsWithIgnCase(key, "--bar-")) {
+ const char* subkey = key + strlen("--bar-");
+ if (ffStrEqualsIgnCase(subkey, "char-elapsed")) {
+ ffOptionParseString(key, value, &options->barCharElapsed);
+ } else if (ffStrEqualsIgnCase(subkey, "char-total")) {
+ ffOptionParseString(key, value, &options->barCharTotal);
+ } else if (ffStrEqualsIgnCase(subkey, "width")) {
+ options->barWidth = (uint8_t) ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subkey, "border-left")) {
+ ffOptionParseString(key, value, &options->barBorderLeft);
+ } else if (ffStrEqualsIgnCase(subkey, "border-right")) {
+ ffOptionParseString(key, value, &options->barBorderRight);
+ } else if (ffStrEqualsIgnCase(subkey, "border-left-elapsed")) {
+ ffOptionParseString(key, value, &options->barBorderLeftElapsed);
+ } else if (ffStrEqualsIgnCase(subkey, "border-right-elapsed")) {
+ ffOptionParseString(key, value, &options->barBorderRightElapsed);
+ } else if (ffStrEqualsIgnCase(subkey, "color-elapsed")) {
+ if (!value) {
+ ffStrbufClear(&options->barColorElapsed);
+ } else if (ffStrEqualsIgnCase(value, "auto")) {
+ ffStrbufSetStatic(&options->barColorElapsed, "auto");
+ } else {
+ ffOptionParseColor(value, &options->barColorElapsed);
+ }
+ } else if (ffStrEqualsIgnCase(subkey, "color-total")) {
+ ffOptionParseColor(value, &options->barColorTotal);
+ } else if (ffStrEqualsIgnCase(subkey, "color-border")) {
+ ffOptionParseColor(value, &options->barColorBorder);
+ } else {
+ return false;
+ }
+ } else if (ffStrStartsWithIgnCase(key, "--freq-")) {
+ const char* subkey = key + strlen("--freq-");
+ if (ffStrEqualsIgnCase(subkey, "ndigits")) {
+ options->freqNdigits = (int8_t) ffOptionParseInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) {
+ options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "default", FF_SPACE_BEFORE_UNIT_DEFAULT },
+ { "always", FF_SPACE_BEFORE_UNIT_ALWAYS },
+ { "never", FF_SPACE_BEFORE_UNIT_NEVER },
+ {},
+ });
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+void ffOptionsInitDisplay(FFOptionsDisplay* options) {
+ ffStrbufInit(&options->colorKeys);
+ ffStrbufInit(&options->colorTitle);
+ ffStrbufInit(&options->colorOutput);
+ ffStrbufInit(&options->colorSeparator);
+ options->brightColor = !instance.state.terminalLightTheme;
+ ffStrbufInitStatic(&options->keyValueSeparator, ": ");
+
+ options->showErrors = false;
+ options->pipe = !isatty(STDOUT_FILENO) || !!getenv("NO_COLOR");
+
+#ifdef NDEBUG
+ options->disableLinewrap = !options->pipe;
+#else
+ options->disableLinewrap = false;
+ options->debugMode = !!getenv("FF_DEBUG");
+#endif
+
+ options->durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
+ options->hideCursor = false;
+ options->sizeBinaryPrefix = FF_SIZE_BINARY_PREFIX_TYPE_IEC;
+ options->sizeNdigits = 2;
+ options->sizeMaxPrefix = 8; // YB
+ options->sizeSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
+
+ options->stat = -1;
+ options->noBuffer = false;
+ options->keyWidth = 0;
+ options->keyPaddingLeft = 0;
+ options->keyType = FF_MODULE_KEY_TYPE_STRING;
+
+ options->tempUnit = FF_TEMPERATURE_UNIT_DEFAULT;
+ options->tempNdigits = 1;
+ ffStrbufInitStatic(&options->tempColorGreen, FF_COLOR_FG_GREEN);
+ ffStrbufInitStatic(&options->tempColorYellow, instance.state.terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW);
+ ffStrbufInitStatic(&options->tempColorRed, instance.state.terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED);
+ options->tempSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
+
+ ffStrbufInitStatic(&options->barCharElapsed, "â– ");
+ ffStrbufInitStatic(&options->barCharTotal, "-");
+ ffStrbufInitStatic(&options->barBorderLeft, "[ ");
+ ffStrbufInitStatic(&options->barBorderRight, " ]");
+ ffStrbufInit(&options->barBorderLeftElapsed);
+ ffStrbufInit(&options->barBorderRightElapsed);
+ ffStrbufInitStatic(&options->barColorElapsed, "auto");
+ ffStrbufInitStatic(&options->barColorTotal, instance.state.terminalLightTheme ? FF_COLOR_FG_WHITE : FF_COLOR_FG_LIGHT_WHITE);
+ ffStrbufInitStatic(&options->barColorBorder, instance.state.terminalLightTheme ? FF_COLOR_FG_WHITE : FF_COLOR_FG_LIGHT_WHITE);
+ options->barWidth = 10;
+
+ options->durationAbbreviation = false;
+ options->durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
+ options->percentType = FF_PERCENTAGE_TYPE_NUM_BIT | FF_PERCENTAGE_TYPE_NUM_COLOR_BIT;
+ options->percentNdigits = 0;
+ ffStrbufInitStatic(&options->percentColorGreen, FF_COLOR_FG_GREEN);
+ ffStrbufInitStatic(&options->percentColorYellow, instance.state.terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW);
+ ffStrbufInitStatic(&options->percentColorRed, instance.state.terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED);
+ options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
+ options->percentWidth = 0;
+
+ options->freqNdigits = 2;
+ options->freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT;
+ options->fractionNdigits = 2;
+ options->fractionTrailingZeros = FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT;
+
+ ffListInit(&options->constants);
+}
+
+void ffOptionsDestroyDisplay(FFOptionsDisplay* options) {
+ ffStrbufDestroy(&options->colorKeys);
+ ffStrbufDestroy(&options->colorTitle);
+ ffStrbufDestroy(&options->colorOutput);
+ ffStrbufDestroy(&options->colorSeparator);
+ ffStrbufDestroy(&options->keyValueSeparator);
+ ffStrbufDestroy(&options->barCharElapsed);
+ ffStrbufDestroy(&options->barCharTotal);
+ FF_LIST_FOR_EACH (FFstrbuf, item, options->constants) {
+ ffStrbufDestroy(item);
+ }
+ ffListDestroy(&options->constants);
+}
+
+void ffOptionsGenerateDisplayJsonConfig(FFdata* data, FFOptionsDisplay* options) {
+ yyjson_mut_doc* doc = data->resultDoc;
+ yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, doc->root, "display");
+
+ if (options->stat <= 0) {
+ yyjson_mut_obj_add_bool(doc, obj, "stat", options->stat == 0);
+ } else {
+ yyjson_mut_obj_add_int(doc, obj, "stat", options->stat);
+ }
+
+ yyjson_mut_obj_add_bool(doc, obj, "pipe", options->pipe);
+
+ yyjson_mut_obj_add_bool(doc, obj, "showErrors", options->showErrors);
+
+ yyjson_mut_obj_add_bool(doc, obj, "disableLinewrap", options->disableLinewrap);
+
+ yyjson_mut_obj_add_bool(doc, obj, "hideCursor", options->hideCursor);
+
+ yyjson_mut_obj_add_strbuf(doc, obj, "separator", &options->keyValueSeparator);
+
+ {
+ yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, obj, "color");
+ yyjson_mut_obj_add_strbuf(doc, color, "keys", &options->colorKeys);
+ yyjson_mut_obj_add_strbuf(doc, color, "title", &options->colorTitle);
+ yyjson_mut_obj_add_strbuf(doc, color, "output", &options->colorOutput);
+ yyjson_mut_obj_add_strbuf(doc, color, "separator", &options->colorSeparator);
+ }
+
+ yyjson_mut_obj_add_bool(doc, obj, "brightColor", options->brightColor);
+
+ {
+ yyjson_mut_val* duration = yyjson_mut_obj_add_obj(doc, obj, "duration");
+ yyjson_mut_obj_add_bool(doc, duration, "abbreviation", options->durationAbbreviation);
+ switch (options->durationSpaceBeforeUnit) {
+ case FF_SPACE_BEFORE_UNIT_DEFAULT:
+ yyjson_mut_obj_add_str(doc, duration, "spaceBeforeUnit", "default");
+ break;
+ case FF_SPACE_BEFORE_UNIT_ALWAYS:
+ yyjson_mut_obj_add_str(doc, duration, "spaceBeforeUnit", "always");
+ break;
+ case FF_SPACE_BEFORE_UNIT_NEVER:
+ yyjson_mut_obj_add_str(doc, duration, "spaceBeforeUnit", "never");
+ break;
+ }
+ }
+
+ {
+ yyjson_mut_val* size = yyjson_mut_obj_add_obj(doc, obj, "size");
+ yyjson_mut_obj_add_str(doc, size, "maxPrefix", ((const char*[]) {
+ "B",
+ "kB",
+ "MB",
+ "GB",
+ "TB",
+ "PB",
+ "EB",
+ "ZB",
+ "YB",
+ })[options->sizeMaxPrefix]);
+ switch (options->sizeBinaryPrefix) {
+ case FF_SIZE_BINARY_PREFIX_TYPE_IEC:
+ yyjson_mut_obj_add_str(doc, size, "binaryPrefix", "iec");
+ break;
+ case FF_SIZE_BINARY_PREFIX_TYPE_SI:
+ yyjson_mut_obj_add_str(doc, size, "binaryPrefix", "si");
+ break;
+ case FF_SIZE_BINARY_PREFIX_TYPE_JEDEC:
+ yyjson_mut_obj_add_str(doc, size, "binaryPrefix", "jedec");
+ break;
+ }
+ yyjson_mut_obj_add_uint(doc, size, "ndigits", options->sizeNdigits);
+ switch (options->sizeSpaceBeforeUnit) {
+ case FF_SPACE_BEFORE_UNIT_DEFAULT:
+ yyjson_mut_obj_add_str(doc, size, "spaceBeforeUnit", "default");
+ break;
+ case FF_SPACE_BEFORE_UNIT_ALWAYS:
+ yyjson_mut_obj_add_str(doc, size, "spaceBeforeUnit", "always");
+ break;
+ case FF_SPACE_BEFORE_UNIT_NEVER:
+ yyjson_mut_obj_add_str(doc, size, "spaceBeforeUnit", "never");
+ break;
+ }
+ }
+
+ {
+ yyjson_mut_val* temperature = yyjson_mut_obj_add_obj(doc, obj, "temp");
+ switch (options->tempUnit) {
+ case FF_TEMPERATURE_UNIT_DEFAULT:
+ yyjson_mut_obj_add_str(doc, temperature, "unit", "D");
+ break;
+ case FF_TEMPERATURE_UNIT_CELSIUS:
+ yyjson_mut_obj_add_str(doc, temperature, "unit", "C");
+ break;
+ case FF_TEMPERATURE_UNIT_FAHRENHEIT:
+ yyjson_mut_obj_add_str(doc, temperature, "unit", "F");
+ break;
+ case FF_TEMPERATURE_UNIT_KELVIN:
+ yyjson_mut_obj_add_str(doc, temperature, "unit", "K");
+ break;
+ }
+ yyjson_mut_obj_add_uint(doc, temperature, "ndigits", options->tempNdigits);
+ {
+ yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, temperature, "color");
+ yyjson_mut_obj_add_strbuf(doc, color, "green", &options->tempColorGreen);
+ yyjson_mut_obj_add_strbuf(doc, color, "yellow", &options->tempColorYellow);
+ yyjson_mut_obj_add_strbuf(doc, color, "red", &options->tempColorRed);
+ }
+ switch (options->tempSpaceBeforeUnit) {
+ case FF_SPACE_BEFORE_UNIT_DEFAULT:
+ yyjson_mut_obj_add_str(doc, temperature, "spaceBeforeUnit", "default");
+ break;
+ case FF_SPACE_BEFORE_UNIT_ALWAYS:
+ yyjson_mut_obj_add_str(doc, temperature, "spaceBeforeUnit", "always");
+ break;
+ case FF_SPACE_BEFORE_UNIT_NEVER:
+ yyjson_mut_obj_add_str(doc, temperature, "spaceBeforeUnit", "never");
+ break;
+ }
+ }
+
+ {
+ yyjson_mut_val* percent = yyjson_mut_obj_add_obj(doc, obj, "percent");
+ {
+ yyjson_mut_val* type = yyjson_mut_obj_add_arr(doc, percent, "type");
+ if (options->percentType & FF_PERCENTAGE_TYPE_NUM_BIT) {
+ yyjson_mut_arr_add_str(doc, type, "num");
+ }
+ if (options->percentType & FF_PERCENTAGE_TYPE_BAR_BIT) {
+ yyjson_mut_arr_add_str(doc, type, "bar");
+ }
+ if (options->percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT) {
+ yyjson_mut_arr_add_str(doc, type, "hide-others");
+ }
+ if (options->percentType & FF_PERCENTAGE_TYPE_NUM_COLOR_BIT) {
+ yyjson_mut_arr_add_str(doc, type, "num-color");
+ }
+ if (options->percentType & FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT) {
+ yyjson_mut_arr_add_str(doc, type, "bar-monochrome");
+ }
+ }
+ yyjson_mut_obj_add_uint(doc, percent, "ndigits", options->percentNdigits);
+ {
+ yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, percent, "color");
+ yyjson_mut_obj_add_strbuf(doc, color, "green", &options->percentColorGreen);
+ yyjson_mut_obj_add_strbuf(doc, color, "yellow", &options->percentColorYellow);
+ yyjson_mut_obj_add_strbuf(doc, color, "red", &options->percentColorRed);
+ }
+ switch (options->percentSpaceBeforeUnit) {
+ case FF_SPACE_BEFORE_UNIT_DEFAULT:
+ yyjson_mut_obj_add_str(doc, percent, "spaceBeforeUnit", "default");
+ break;
+ case FF_SPACE_BEFORE_UNIT_ALWAYS:
+ yyjson_mut_obj_add_str(doc, percent, "spaceBeforeUnit", "always");
+ break;
+ case FF_SPACE_BEFORE_UNIT_NEVER:
+ yyjson_mut_obj_add_str(doc, percent, "spaceBeforeUnit", "never");
+ break;
+ }
+ yyjson_mut_obj_add_uint(doc, percent, "width", options->percentWidth);
+ }
+
+ {
+ yyjson_mut_val* bar = yyjson_mut_obj_add_obj(doc, obj, "bar");
+
+ yyjson_mut_val* char_ = yyjson_mut_obj_add_obj(doc, bar, "char");
+ yyjson_mut_obj_add_strbuf(doc, char_, "elapsed", &options->barCharElapsed);
+ yyjson_mut_obj_add_strbuf(doc, char_, "total", &options->barCharTotal);
+
+ yyjson_mut_val* border = yyjson_mut_obj_add_obj(doc, bar, "border");
+ yyjson_mut_obj_add_strbuf(doc, border, "left", &options->barBorderLeft);
+ yyjson_mut_obj_add_strbuf(doc, border, "right", &options->barBorderRight);
+ yyjson_mut_obj_add_strbuf(doc, border, "leftElapsed", &options->barBorderLeftElapsed);
+ yyjson_mut_obj_add_strbuf(doc, border, "rightElapsed", &options->barBorderRightElapsed);
+
+ yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, bar, "color");
+ yyjson_mut_obj_add_strbuf(doc, color, "elapsed", &options->barColorElapsed);
+ yyjson_mut_obj_add_strbuf(doc, color, "total", &options->barColorTotal);
+ yyjson_mut_obj_add_strbuf(doc, color, "border", &options->barColorBorder);
+
+ yyjson_mut_obj_add_uint(doc, bar, "width", options->barWidth);
+ }
+
+ {
+ yyjson_mut_val* fraction = yyjson_mut_obj_add_obj(doc, obj, "fraction");
+
+ if (options->fractionNdigits < 0) {
+ yyjson_mut_obj_add_null(doc, fraction, "ndigits");
+ } else {
+ yyjson_mut_obj_add_uint(doc, fraction, "ndigits", (uint8_t) options->fractionNdigits);
+ }
+
+ switch (options->fractionTrailingZeros) {
+ case FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT:
+ yyjson_mut_obj_add_str(doc, fraction, "trailingZeros", "default");
+ break;
+ case FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS:
+ yyjson_mut_obj_add_str(doc, fraction, "trailingZeros", "always");
+ break;
+ case FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER:
+ yyjson_mut_obj_add_str(doc, fraction, "trailingZeros", "never");
+ break;
+ }
+ }
+
+ yyjson_mut_obj_add_bool(doc, obj, "noBuffer", options->noBuffer);
+
+ {
+ yyjson_mut_val* key = yyjson_mut_obj_add_obj(doc, obj, "key");
+ yyjson_mut_obj_add_uint(doc, key, "width", options->keyWidth);
+ switch ((uint8_t) options->keyType) {
+ case FF_MODULE_KEY_TYPE_NONE:
+ yyjson_mut_obj_add_str(doc, key, "type", "none");
+ break;
+ case FF_MODULE_KEY_TYPE_STRING:
+ yyjson_mut_obj_add_str(doc, key, "type", "string");
+ break;
+ case FF_MODULE_KEY_TYPE_ICON:
+ yyjson_mut_obj_add_str(doc, key, "type", "icon");
+ break;
+ case FF_MODULE_KEY_TYPE_BOTH_0:
+ yyjson_mut_obj_add_str(doc, key, "type", "both-0");
+ break;
+ case FF_MODULE_KEY_TYPE_BOTH_1: // alias: both
+ yyjson_mut_obj_add_str(doc, key, "type", "both");
+ break;
+ case FF_MODULE_KEY_TYPE_BOTH_2:
+ yyjson_mut_obj_add_str(doc, key, "type", "both-2");
+ break;
+ case FF_MODULE_KEY_TYPE_BOTH_3:
+ yyjson_mut_obj_add_str(doc, key, "type", "both-3");
+ break;
+ case FF_MODULE_KEY_TYPE_BOTH_4:
+ yyjson_mut_obj_add_str(doc, key, "type", "both-4");
+ break;
+ }
+
+ yyjson_mut_obj_add_uint(doc, key, "paddingLeft", options->keyPaddingLeft);
+ }
+
+ {
+ yyjson_mut_val* freq = yyjson_mut_obj_add_obj(doc, obj, "freq");
+ yyjson_mut_obj_add_int(doc, freq, "ndigits", options->freqNdigits);
+ switch (options->freqSpaceBeforeUnit) {
+ case FF_SPACE_BEFORE_UNIT_DEFAULT:
+ yyjson_mut_obj_add_str(doc, freq, "spaceBeforeUnit", "default");
+ break;
+ case FF_SPACE_BEFORE_UNIT_ALWAYS:
+ yyjson_mut_obj_add_str(doc, freq, "spaceBeforeUnit", "always");
+ break;
+ case FF_SPACE_BEFORE_UNIT_NEVER:
+ yyjson_mut_obj_add_str(doc, freq, "spaceBeforeUnit", "never");
+ break;
+ }
+ }
+
+ {
+ yyjson_mut_val* constants = yyjson_mut_obj_add_arr(doc, obj, "constants");
+ FF_LIST_FOR_EACH (FFstrbuf, item, options->constants) {
+ yyjson_mut_arr_add_strbuf(doc, constants, item);
+ }
+ }
+}
diff --git a/src/options/display.h b/src/options/display.h
new file mode 100644
index 0000000..8c674ea
--- /dev/null
+++ b/src/options/display.h
@@ -0,0 +1,97 @@
+#pragma once
+
+#include "common/ffdata.h"
+#include "common/percent.h"
+#include "common/FFstrbuf.h"
+#include "common/FFlist.h"
+
+typedef enum FF_A_PACKED FFSizeBinaryPrefixType {
+ FF_SIZE_BINARY_PREFIX_TYPE_IEC, // 1024 Bytes = 1 KiB, 1024 KiB = 1 MiB, ... (standard)
+ FF_SIZE_BINARY_PREFIX_TYPE_SI, // 1000 Bytes = 1 kB, 1000 kB = 1 MB, ...
+ FF_SIZE_BINARY_PREFIX_TYPE_JEDEC, // 1024 Bytes = 1 KB, 1024 KB = 1 MB, ...
+} FFSizeBinaryPrefixType;
+
+typedef enum FF_A_PACKED FFTemperatureUnit {
+ FF_TEMPERATURE_UNIT_DEFAULT,
+ FF_TEMPERATURE_UNIT_CELSIUS,
+ FF_TEMPERATURE_UNIT_FAHRENHEIT,
+ FF_TEMPERATURE_UNIT_KELVIN,
+} FFTemperatureUnit;
+
+typedef enum FF_A_PACKED FFSpaceBeforeUnitType {
+ FF_SPACE_BEFORE_UNIT_DEFAULT,
+ FF_SPACE_BEFORE_UNIT_ALWAYS,
+ FF_SPACE_BEFORE_UNIT_NEVER,
+} FFSpaceBeforeUnitType;
+
+typedef enum FF_A_PACKED FFFractionTrailingZerosType {
+ FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT,
+ FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS,
+ FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER,
+} FFFractionTrailingZerosType;
+
+typedef struct FFOptionsDisplay {
+ // If one of those is empty, ffLogoPrint will set them
+ FFstrbuf colorKeys;
+ FFstrbuf colorTitle;
+ FFstrbuf colorOutput;
+ FFstrbuf colorSeparator;
+
+ bool brightColor;
+
+ FFstrbuf keyValueSeparator;
+
+ int32_t stat; // <0: disable stat; 0: no threshold; >0: threshold in ms
+ bool pipe; // disables all escape sequences
+ bool showErrors;
+#ifndef NDEBUG
+ bool debugMode;
+#endif
+ bool disableLinewrap;
+ bool durationAbbreviation;
+ FFSpaceBeforeUnitType durationSpaceBeforeUnit;
+ bool hideCursor;
+ FFSizeBinaryPrefixType sizeBinaryPrefix;
+ uint8_t sizeNdigits;
+ uint8_t sizeMaxPrefix;
+ FFSpaceBeforeUnitType sizeSpaceBeforeUnit;
+ FFTemperatureUnit tempUnit;
+ uint8_t tempNdigits;
+ FFstrbuf tempColorGreen;
+ FFstrbuf tempColorYellow;
+ FFstrbuf tempColorRed;
+ FFSpaceBeforeUnitType tempSpaceBeforeUnit;
+ FFstrbuf barCharElapsed;
+ FFstrbuf barCharTotal;
+ FFstrbuf barBorderLeft;
+ FFstrbuf barBorderRight;
+ FFstrbuf barBorderLeftElapsed;
+ FFstrbuf barBorderRightElapsed;
+ FFstrbuf barColorElapsed; // "auto" for auto selection from percent config; empty for no custom color (inherits)
+ FFstrbuf barColorTotal; // empty for no custom color (inherits)
+ FFstrbuf barColorBorder; // empty for no custom color (inherits)
+ uint8_t barWidth;
+ FFPercentageTypeFlags percentType;
+ uint8_t percentNdigits;
+ FFstrbuf percentColorGreen;
+ FFstrbuf percentColorYellow;
+ FFstrbuf percentColorRed;
+ FFSpaceBeforeUnitType percentSpaceBeforeUnit;
+ uint8_t percentWidth;
+ bool noBuffer;
+ FFModuleKeyType keyType;
+ uint16_t keyWidth;
+ uint16_t keyPaddingLeft;
+ int8_t freqNdigits;
+ FFSpaceBeforeUnitType freqSpaceBeforeUnit;
+ int8_t fractionNdigits;
+ FFFractionTrailingZerosType fractionTrailingZeros;
+
+ FFlist constants; // list of FFstrbuf
+} FFOptionsDisplay;
+
+const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_val* root);
+bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key, const char* value);
+void ffOptionsInitDisplay(FFOptionsDisplay* options);
+void ffOptionsDestroyDisplay(FFOptionsDisplay* options);
+void ffOptionsGenerateDisplayJsonConfig(FFdata* data, FFOptionsDisplay* options);
diff --git a/src/options/general.c b/src/options/general.c
new file mode 100644
index 0000000..6257a0f
--- /dev/null
+++ b/src/options/general.c
@@ -0,0 +1,134 @@
+#include "fastfetch.h"
+#include "common/jsonconfig.h"
+#include "common/processing.h"
+#include "common/strutil.h"
+#include "options/general.h"
+
+#include <unistd.h>
+
+const char* ffOptionsParseGeneralJsonConfig(FFOptionsGeneral* options, yyjson_val* root) {
+ yyjson_val* object = yyjson_obj_get(root, "general");
+ if (!object) {
+ return NULL;
+ }
+ if (!yyjson_is_obj(object)) {
+ return "Property 'general' must be an object";
+ }
+
+ yyjson_val *key, *val;
+ size_t idx, max;
+ yyjson_obj_foreach (object, idx, max, key, val) {
+ if (unsafe_yyjson_equals_str(key, "thread")) {
+ options->multithreading = yyjson_get_bool(val);
+ } else if (unsafe_yyjson_equals_str(key, "processingTimeout")) {
+ options->processingTimeout = (int32_t) yyjson_get_int(val);
+ } else if (unsafe_yyjson_equals_str(key, "preRun")) {
+ if (!yyjson_is_str(val)) {
+ return "general.preRun must be a string";
+ }
+ if (system(unsafe_yyjson_get_str(val)) < 0) {
+ return "Failed to execute preRun command";
+ }
+ } else if (unsafe_yyjson_equals_str(key, "detectVersion")) {
+ options->detectVersion = yyjson_get_bool(val);
+ } else if (unsafe_yyjson_equals_str(key, "playerName")) {
+ ffStrbufSetJsonVal(&options->playerName, val);
+ }
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)
+ else if (unsafe_yyjson_equals_str(key, "dsForceDrm")) {
+ if (yyjson_is_str(val)) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
+ { "sysfs-only", FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY },
+ { "false", FF_DS_FORCE_DRM_TYPE_FALSE },
+ { "true", FF_DS_FORCE_DRM_TYPE_TRUE },
+ {},
+ });
+ if (error) {
+ return "Invalid enum value of `dsForceDrm`";
+ } else {
+ options->dsForceDrm = (FFDsForceDrmType) value;
+ }
+ } else {
+ options->dsForceDrm = yyjson_get_bool(val) ? FF_DS_FORCE_DRM_TYPE_TRUE : FF_DS_FORCE_DRM_TYPE_FALSE;
+ }
+ }
+#endif
+ else {
+ return "Unknown general property";
+ }
+ }
+
+ return NULL;
+}
+
+bool ffOptionsParseGeneralCommandLine(FFOptionsGeneral* options, const char* key, const char* value) {
+ if (ffStrEqualsIgnCase(key, "--thread") || ffStrEqualsIgnCase(key, "--multithreading")) {
+ options->multithreading = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(key, "--processing-timeout")) {
+ options->processingTimeout = ffOptionParseInt32(key, value);
+ } else if (ffStrEqualsIgnCase(key, "--detect-version")) {
+ options->detectVersion = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(key, "--player-name")) {
+ ffOptionParseString(key, value, &options->playerName);
+ }
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)
+ else if (ffStrEqualsIgnCase(key, "--ds-force-drm")) {
+ if (ffOptionParseBoolean(value)) {
+ options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_TRUE;
+ } else if (ffStrEqualsIgnCase(value, "sysfs-only")) {
+ options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY;
+ } else {
+ options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_FALSE;
+ }
+ }
+#endif
+ else {
+ return false;
+ }
+
+ return true;
+}
+
+void ffOptionsInitGeneral(FFOptionsGeneral* options) {
+ options->processingTimeout = 5000;
+ options->multithreading = true;
+ options->detectVersion = true;
+ ffStrbufInit(&options->playerName);
+
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)
+ options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_FALSE;
+#endif
+}
+
+void ffOptionsDestroyGeneral(FFOptionsGeneral* options) {
+ ffStrbufDestroy(&options->playerName);
+}
+
+void ffOptionsGenerateGeneralJsonConfig(FFdata* data, FFOptionsGeneral* options) {
+ yyjson_mut_doc* doc = data->resultDoc;
+ yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, doc->root, "general");
+
+ yyjson_mut_obj_add_bool(doc, obj, "thread", options->multithreading);
+
+ yyjson_mut_obj_add_int(doc, obj, "processingTimeout", options->processingTimeout);
+
+ yyjson_mut_obj_add_bool(doc, obj, "detectVersion", options->detectVersion);
+
+ yyjson_mut_obj_add_strbuf(doc, obj, "playerName", &options->playerName);
+
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)
+
+ switch (options->dsForceDrm) {
+ case FF_DS_FORCE_DRM_TYPE_FALSE:
+ yyjson_mut_obj_add_bool(doc, obj, "dsForceDrm", false);
+ break;
+ case FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY:
+ yyjson_mut_obj_add_str(doc, obj, "dsForceDrm", "sysfs-only");
+ break;
+ case FF_DS_FORCE_DRM_TYPE_TRUE:
+ yyjson_mut_obj_add_bool(doc, obj, "dsForceDrm", true);
+ break;
+ }
+#endif
+}
diff --git a/src/options/general.h b/src/options/general.h
new file mode 100644
index 0000000..454ca60
--- /dev/null
+++ b/src/options/general.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "common/ffdata.h"
+
+typedef enum FF_A_PACKED FFDsForceDrmType {
+ FF_DS_FORCE_DRM_TYPE_FALSE = 0, // Disable
+ FF_DS_FORCE_DRM_TYPE_TRUE = 1, // Try `libdrm`, then `sysfs` if libdrm failed
+ FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY, // Use `/sys/class/drm` only
+} FFDsForceDrmType;
+
+typedef struct FFOptionsGeneral {
+ bool multithreading;
+ int32_t processingTimeout;
+ bool detectVersion;
+ FFstrbuf playerName;
+
+// Module options that cannot be put in module option structure
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)
+ FFDsForceDrmType dsForceDrm;
+#endif
+} FFOptionsGeneral;
+
+const char* ffOptionsParseGeneralJsonConfig(FFOptionsGeneral* options, yyjson_val* root);
+bool ffOptionsParseGeneralCommandLine(FFOptionsGeneral* options, const char* key, const char* value);
+void ffOptionsInitGeneral(FFOptionsGeneral* options);
+void ffOptionsDestroyGeneral(FFOptionsGeneral* options);
+void ffOptionsGenerateGeneralJsonConfig(FFdata* data, FFOptionsGeneral* options);
diff --git a/src/options/logo.c b/src/options/logo.c
new file mode 100644
index 0000000..30fbccd
--- /dev/null
+++ b/src/options/logo.c
@@ -0,0 +1,562 @@
+#include "logo/logo.h"
+
+#include "common/jsonconfig.h"
+#include "common/strutil.h"
+
+void ffOptionsInitLogo(FFOptionsLogo* options) {
+ ffStrbufInit(&options->source);
+ options->type = FF_LOGO_TYPE_AUTO;
+ for (uint8_t i = 0; i < (uint8_t) FASTFETCH_LOGO_MAX_COLORS; ++i) {
+ ffStrbufInit(&options->colors[i]);
+ }
+ options->width = 0;
+ options->height = 0; // preserve aspect ratio
+ options->paddingTop = 0;
+ options->paddingBottom = 0;
+ options->paddingLeft = 0;
+ options->paddingRight = 4;
+ options->printRemaining = true;
+ options->preserveAspectRatio = false;
+ options->recache = false;
+ options->position = FF_LOGO_POSITION_LEFT;
+
+#if FF_HAVE_CHAFA
+ options->chafaFgOnly = false;
+ ffStrbufInitStatic(&options->chafaSymbols, "block+border+space-wide-inverted"); // Chafa default
+ options->chafaCanvasMode = UINT32_MAX;
+ options->chafaColorSpace = UINT32_MAX;
+ options->chafaDitherMode = UINT32_MAX;
+#endif
+}
+
+bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, const char* value) {
+ if (ffStrEqualsIgnCase(key, "-l")) {
+ goto logoType;
+ }
+
+ const char* subKey = ffOptionTestPrefix(key, "logo");
+ if (subKey) {
+ if (subKey[0] == '\0') {
+ logoType:
+ if (value == NULL) {
+ fprintf(stderr, "Error: usage: %s <none|small|logo-source>\n", key);
+ exit(477);
+ }
+ // this is usually wanted when disabling logo
+ if (ffStrEqualsIgnCase(value, "none")) {
+ options->type = FF_LOGO_TYPE_NONE;
+ } else if (ffStrEqualsIgnCase(value, "small")) {
+ options->type = FF_LOGO_TYPE_SMALL;
+ } else {
+ ffOptionParseString(key, value, &options->source);
+ }
+ } else if (ffStrEqualsIgnCase(subKey, "type")) {
+ options->type = (FFLogoType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "auto", FF_LOGO_TYPE_AUTO },
+ { "builtin", FF_LOGO_TYPE_BUILTIN },
+ { "small", FF_LOGO_TYPE_SMALL },
+ { "file", FF_LOGO_TYPE_FILE },
+ { "file-raw", FF_LOGO_TYPE_FILE_RAW },
+ { "data", FF_LOGO_TYPE_DATA },
+ { "data-raw", FF_LOGO_TYPE_DATA_RAW },
+ { "command-raw", FF_LOGO_TYPE_COMMAND_RAW },
+ { "sixel", FF_LOGO_TYPE_IMAGE_SIXEL },
+ { "kitty", FF_LOGO_TYPE_IMAGE_KITTY },
+ { "kitty-direct", FF_LOGO_TYPE_IMAGE_KITTY_DIRECT },
+ { "kitty-icat", FF_LOGO_TYPE_IMAGE_KITTY_ICAT },
+ { "iterm", FF_LOGO_TYPE_IMAGE_ITERM },
+ { "chafa", FF_LOGO_TYPE_IMAGE_CHAFA },
+ { "raw", FF_LOGO_TYPE_IMAGE_RAW },
+ { "none", FF_LOGO_TYPE_NONE },
+ {},
+ });
+ } else if (ffStrStartsWithIgnCase(subKey, "color-") && subKey[6] != '\0' && subKey[7] == '\0') // matches "--logo-color-*"
+ {
+ // Map the number to an array index, so that '1' -> 0, '2' -> 1, etc.
+ int index = (int) subKey[6] - '0' - 1;
+
+ // Match only --logo-color-[1-9]
+ if (index < 0 || index >= FASTFETCH_LOGO_MAX_COLORS) {
+ fprintf(stderr, "Error: invalid --color-[1-9] index: %c\n", key[13]);
+ exit(472);
+ }
+ if (value == NULL) {
+ fprintf(stderr, "Error: usage: %s <str>\n", key);
+ exit(477);
+ }
+ ffOptionParseColor(value, &options->colors[index]);
+ } else if (ffStrEqualsIgnCase(subKey, "width")) {
+ options->width = ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subKey, "height")) {
+ options->height = ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subKey, "padding")) {
+ uint32_t padding = ffOptionParseUInt32(key, value);
+ options->paddingLeft = padding;
+ options->paddingRight = padding;
+ } else if (ffStrEqualsIgnCase(subKey, "padding-top")) {
+ options->paddingTop = ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subKey, "padding-bottom")) {
+ options->paddingBottom = ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subKey, "padding-left")) {
+ options->paddingLeft = ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subKey, "padding-right")) {
+ options->paddingRight = ffOptionParseUInt32(key, value);
+ } else if (ffStrEqualsIgnCase(subKey, "print-remaining")) {
+ options->printRemaining = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(subKey, "preserve-aspect-ratio")) {
+ options->preserveAspectRatio = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(subKey, "recache")) {
+ options->recache = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(subKey, "separate")) {
+ fputs("--logo-separate has been renamed to --logo-position\n", stderr);
+ exit(477);
+ } else if (ffStrEqualsIgnCase(subKey, "position")) {
+ options->position = (FFLogoPosition) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "left", FF_LOGO_POSITION_LEFT },
+ { "right", FF_LOGO_POSITION_RIGHT },
+ { "top", FF_LOGO_POSITION_TOP },
+ {},
+ });
+ } else {
+ return false;
+ }
+ } else if ((subKey = ffOptionTestPrefix(key, "file"))) {
+ if (subKey[0] == '\0') {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_FILE;
+ } else if (ffStrEqualsIgnCase(subKey, "raw")) {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_FILE_RAW;
+ } else {
+ return false;
+ }
+ } else if ((subKey = ffOptionTestPrefix(key, "data"))) {
+ if (subKey[0] == '\0') {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_DATA;
+ } else if (ffStrEqualsIgnCase(subKey, "raw")) {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_DATA_RAW;
+ } else {
+ return false;
+ }
+ } else if (ffStrEqualsIgnCase(key, "--sixel")) {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_IMAGE_SIXEL;
+ } else if (ffStrEqualsIgnCase(key, "--kitty")) {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_IMAGE_KITTY;
+ } else if (ffStrEqualsIgnCase(key, "--kitty-direct")) {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_IMAGE_KITTY_DIRECT;
+ } else if (ffStrEqualsIgnCase(key, "--kitty-icat")) {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_IMAGE_KITTY_ICAT;
+ } else if (ffStrEqualsIgnCase(key, "--iterm")) {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_IMAGE_ITERM;
+ } else if (ffStrEqualsIgnCase(key, "--raw")) {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_IMAGE_RAW;
+ } else if ((subKey = ffOptionTestPrefix(key, "chafa"))) {
+ if (subKey[0] == '\0') {
+ ffOptionParseString(key, value, &options->source);
+ options->type = FF_LOGO_TYPE_IMAGE_CHAFA;
+#if FF_HAVE_CHAFA
+ } else if (ffStrEqualsIgnCase(subKey, "fg-only")) {
+ options->chafaFgOnly = ffOptionParseBoolean(value);
+ } else if (ffStrEqualsIgnCase(subKey, "symbols")) {
+ ffOptionParseString(key, value, &options->chafaSymbols);
+ } else if (ffStrEqualsIgnCase(subKey, "canvas-mode")) {
+ options->chafaCanvasMode = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "TRUECOLOR", 0 },
+ { "INDEXED_256", 1 },
+ { "INDEXED_240", 2 },
+ { "INDEXED_16", 3 },
+ { "FGBG_BGFG", 4 },
+ { "FGBG", 5 },
+ { "INDEXED_8", 6 },
+ { "INDEXED_16_8", 7 },
+ {},
+ });
+ } else if (ffStrEqualsIgnCase(subKey, "color-space")) {
+ options->chafaColorSpace = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "RGB", 0 },
+ { "DIN99D", 1 },
+ {},
+ });
+ } else if (ffStrEqualsIgnCase(subKey, "dither-mode")) {
+ options->chafaDitherMode = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
+ { "NONE", 0 },
+ { "ORDERED", 1 },
+ { "DIFFUSION", 2 },
+ {},
+ });
+#endif
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+void ffOptionsDestroyLogo(FFOptionsLogo* options) {
+ ffStrbufDestroy(&options->source);
+#if FF_HAVE_CHAFA
+ ffStrbufDestroy(&options->chafaSymbols);
+#endif
+ for (uint8_t i = 0; i < (uint8_t) FASTFETCH_LOGO_MAX_COLORS; ++i) {
+ ffStrbufDestroy(&options->colors[i]);
+ }
+}
+
+const char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* root) {
+ yyjson_val* object = yyjson_obj_get(root, "logo");
+ if (!object) {
+ return NULL;
+ }
+ if (yyjson_is_null(object)) {
+ options->type = FF_LOGO_TYPE_NONE;
+ options->paddingTop = 0;
+ options->paddingBottom = 0;
+ options->paddingRight = 0;
+ options->paddingLeft = 0;
+ return NULL;
+ }
+
+ if (yyjson_is_str(object)) {
+ ffStrbufSetJsonVal(&options->source, object);
+ return NULL;
+ }
+
+ if (!yyjson_is_obj(object)) {
+ return "Property 'logo' must be an object";
+ }
+
+ yyjson_val *key, *val;
+ size_t idx, max;
+ yyjson_obj_foreach (object, idx, max, key, val) {
+ if (unsafe_yyjson_equals_str(key, "type")) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
+ { "auto", FF_LOGO_TYPE_AUTO },
+ { "builtin", FF_LOGO_TYPE_BUILTIN },
+ { "small", FF_LOGO_TYPE_SMALL },
+ { "file", FF_LOGO_TYPE_FILE },
+ { "file-raw", FF_LOGO_TYPE_FILE_RAW },
+ { "data", FF_LOGO_TYPE_DATA },
+ { "data-raw", FF_LOGO_TYPE_DATA_RAW },
+ { "command-raw", FF_LOGO_TYPE_COMMAND_RAW },
+ { "sixel", FF_LOGO_TYPE_IMAGE_SIXEL },
+ { "kitty", FF_LOGO_TYPE_IMAGE_KITTY },
+ { "kitty-direct", FF_LOGO_TYPE_IMAGE_KITTY_DIRECT },
+ { "kitty-icat", FF_LOGO_TYPE_IMAGE_KITTY_ICAT },
+ { "iterm", FF_LOGO_TYPE_IMAGE_ITERM },
+ { "chafa", FF_LOGO_TYPE_IMAGE_CHAFA },
+ { "raw", FF_LOGO_TYPE_IMAGE_RAW },
+ { "none", FF_LOGO_TYPE_NONE },
+ {},
+ });
+
+ if (error) {
+ return error;
+ }
+ options->type = (FFLogoType) value;
+ continue;
+ } else if (unsafe_yyjson_equals_str(key, "source")) {
+ ffStrbufSetJsonVal(&options->source, val);
+ continue;
+ } else if (unsafe_yyjson_equals_str(key, "color")) {
+ if (!yyjson_is_obj(val)) {
+ return "Property 'color' must be an object";
+ }
+
+ yyjson_val *keyc, *valc;
+ size_t idxc, maxc;
+ yyjson_obj_foreach (val, idxc, maxc, keyc, valc) {
+ uint32_t index = (uint32_t) strtoul(unsafe_yyjson_get_str(keyc), NULL, 10);
+ if (index < 1 || index > FASTFETCH_LOGO_MAX_COLORS) {
+ return "Keys of property 'color' must be a number between 1 to 9";
+ }
+
+ ffOptionParseColor(yyjson_get_str(valc), &options->colors[index - 1]);
+ }
+ continue;
+ } else if (unsafe_yyjson_equals_str(key, "width")) {
+ if (yyjson_is_null(val)) {
+ options->width = 0;
+ } else {
+ uint32_t value = (uint32_t) yyjson_get_uint(val);
+ if (value == 0) {
+ return "Logo width must be a positive integer";
+ }
+ options->width = value;
+ }
+ continue;
+ } else if (unsafe_yyjson_equals_str(key, "height")) {
+ if (yyjson_is_null(val)) {
+ options->height = 0;
+ } else {
+ uint32_t value = (uint32_t) yyjson_get_uint(val);
+ if (value == 0) {
+ return "Logo height must be a positive integer";
+ }
+ options->height = value;
+ }
+ continue;
+ } else if (unsafe_yyjson_equals_str(key, "padding")) {
+ if (!yyjson_is_obj(val)) {
+ return "Logo padding must be an object";
+ }
+
+#define FF_PARSE_PADDING_POSITON(pos, paddingPos) \
+ yyjson_val* pos = yyjson_obj_get(val, #pos); \
+ if (pos) { \
+ if (!yyjson_is_uint(pos)) \
+ return "Logo padding values must be positive integers"; \
+ options->paddingPos = (uint32_t) yyjson_get_uint(pos); \
+ }
+ FF_PARSE_PADDING_POSITON(left, paddingLeft);
+ FF_PARSE_PADDING_POSITON(top, paddingTop);
+ FF_PARSE_PADDING_POSITON(right, paddingRight);
+ FF_PARSE_PADDING_POSITON(bottom, paddingBottom);
+#undef FF_PARSE_PADDING_POSITON
+ continue;
+ } else if (unsafe_yyjson_equals_str(key, "printRemaining")) {
+ options->printRemaining = yyjson_get_bool(val);
+ continue;
+ } else if (unsafe_yyjson_equals_str(key, "preserveAspectRatio")) {
+ options->preserveAspectRatio = yyjson_get_bool(val);
+ continue;
+ } else if (unsafe_yyjson_equals_str(key, "recache")) {
+ options->recache = yyjson_get_bool(val);
+ continue;
+ } else if (unsafe_yyjson_equals_str(key, "position")) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
+ { "left", FF_LOGO_POSITION_LEFT },
+ { "top", FF_LOGO_POSITION_TOP },
+ { "right", FF_LOGO_POSITION_RIGHT },
+ {},
+ });
+
+ if (error) {
+ return error;
+ }
+ options->position = (FFLogoPosition) value;
+ continue;
+#if FF_HAVE_CHAFA
+ } else if (unsafe_yyjson_equals_str(key, "chafa")) {
+ if (!yyjson_is_obj(val)) {
+ return "Chafa config must be an object";
+ }
+
+ yyjson_val* fgOnly = yyjson_obj_get(val, "fgOnly");
+ if (fgOnly) {
+ options->chafaFgOnly = yyjson_get_bool(fgOnly);
+ }
+
+ yyjson_val* symbols = yyjson_obj_get(val, "symbols");
+ if (symbols) {
+ ffStrbufSetJsonVal(&options->chafaSymbols, symbols);
+ }
+
+ yyjson_val* canvasMode = yyjson_obj_get(val, "canvasMode");
+ if (canvasMode) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(canvasMode, &value, (FFKeyValuePair[]) {
+ { "TRUECOLOR", 0 },
+ { "INDEXED_256", 1 },
+ { "INDEXED_240", 2 },
+ { "INDEXED_16", 3 },
+ { "FGBG_BGFG", 4 },
+ { "FGBG", 5 },
+ { "INDEXED_8", 6 },
+ { "INDEXED_16_8", 7 },
+ {},
+ });
+
+ if (error) {
+ return error;
+ }
+ options->chafaCanvasMode = (uint32_t) value;
+ }
+
+ yyjson_val* colorSpace = yyjson_obj_get(val, "colorSpace");
+ if (colorSpace) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(colorSpace, &value, (FFKeyValuePair[]) {
+ { "RGB", 0 },
+ { "DIN99D", 1 },
+ {},
+ });
+
+ if (error) {
+ return error;
+ }
+ options->chafaColorSpace = (uint32_t) value;
+ }
+
+ yyjson_val* ditherMode = yyjson_obj_get(val, "ditherMode");
+ if (ditherMode) {
+ int value;
+ const char* error = ffJsonConfigParseEnum(ditherMode, &value, (FFKeyValuePair[]) {
+ { "NONE", 0 },
+ { "ORDERED", 1 },
+ { "DIFFUSION", 2 },
+ {},
+ });
+
+ if (error) {
+ return error;
+ }
+ options->chafaDitherMode = (uint32_t) value;
+ }
+ continue;
+#endif
+ } else {
+ return "Unknown logo key";
+ }
+ }
+
+ return NULL;
+}
+
+void ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options) {
+ yyjson_mut_doc* doc = data->resultDoc;
+ yyjson_mut_val* obj = yyjson_mut_obj(doc);
+
+ switch (options->type) {
+ case FF_LOGO_TYPE_NONE:
+ yyjson_mut_obj_add_null(doc, doc->root, "logo");
+ return;
+ case FF_LOGO_TYPE_BUILTIN:
+ yyjson_mut_obj_add_str(doc, obj, "type", "builtin");
+ break;
+ case FF_LOGO_TYPE_SMALL:
+ yyjson_mut_obj_add_str(doc, obj, "type", "small");
+ break;
+ case FF_LOGO_TYPE_FILE:
+ yyjson_mut_obj_add_str(doc, obj, "type", "file");
+ break;
+ case FF_LOGO_TYPE_FILE_RAW:
+ yyjson_mut_obj_add_str(doc, obj, "type", "file-raw");
+ break;
+ case FF_LOGO_TYPE_DATA:
+ yyjson_mut_obj_add_str(doc, obj, "type", "data");
+ break;
+ case FF_LOGO_TYPE_DATA_RAW:
+ yyjson_mut_obj_add_str(doc, obj, "type", "data-raw");
+ break;
+ case FF_LOGO_TYPE_COMMAND_RAW:
+ yyjson_mut_obj_add_str(doc, obj, "type", "command-raw");
+ break;
+ case FF_LOGO_TYPE_IMAGE_SIXEL:
+ yyjson_mut_obj_add_str(doc, obj, "type", "sixel");
+ break;
+ case FF_LOGO_TYPE_IMAGE_KITTY:
+ yyjson_mut_obj_add_str(doc, obj, "type", "kitty");
+ break;
+ case FF_LOGO_TYPE_IMAGE_KITTY_DIRECT:
+ yyjson_mut_obj_add_str(doc, obj, "type", "kitty-direct");
+ break;
+ case FF_LOGO_TYPE_IMAGE_KITTY_ICAT:
+ yyjson_mut_obj_add_str(doc, obj, "type", "kitty-icat");
+ break;
+ case FF_LOGO_TYPE_IMAGE_ITERM:
+ yyjson_mut_obj_add_str(doc, obj, "type", "iterm");
+ break;
+ case FF_LOGO_TYPE_IMAGE_CHAFA:
+ yyjson_mut_obj_add_str(doc, obj, "type", "chafa");
+ break;
+ case FF_LOGO_TYPE_IMAGE_RAW:
+ yyjson_mut_obj_add_str(doc, obj, "type", "raw");
+ break;
+ default:
+ yyjson_mut_obj_add_str(doc, obj, "type", "auto");
+ break;
+ }
+
+ yyjson_mut_obj_add_str(doc, obj, "source", options->source.chars);
+
+ {
+ yyjson_mut_val* color = yyjson_mut_obj(doc);
+ for (int i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) {
+ char c = (char) ('1' + i);
+ yyjson_mut_obj_add(color, yyjson_mut_strncpy(doc, &c, 1), yyjson_mut_strbuf(doc, &options->colors[i]));
+ }
+ yyjson_mut_obj_add_val(doc, obj, "color", color);
+ }
+
+ if (options->width == 0) {
+ yyjson_mut_obj_add_null(doc, obj, "width");
+ } else {
+ yyjson_mut_obj_add_uint(doc, obj, "width", options->width);
+ }
+
+ if (options->height == 0) {
+ yyjson_mut_obj_add_null(doc, obj, "height");
+ } else {
+ yyjson_mut_obj_add_uint(doc, obj, "height", options->height);
+ }
+
+ {
+ yyjson_mut_val* padding = yyjson_mut_obj_add_obj(doc, obj, "padding");
+ yyjson_mut_obj_add_uint(doc, padding, "top", options->paddingTop);
+ yyjson_mut_obj_add_uint(doc, padding, "left", options->paddingLeft);
+ yyjson_mut_obj_add_uint(doc, padding, "bottom", options->paddingBottom);
+ yyjson_mut_obj_add_uint(doc, padding, "right", options->paddingRight);
+ }
+
+ yyjson_mut_obj_add_bool(doc, obj, "printRemaining", options->printRemaining);
+
+ yyjson_mut_obj_add_bool(doc, obj, "preserveAspectRatio", options->preserveAspectRatio);
+
+ yyjson_mut_obj_add_bool(doc, obj, "recache", options->recache);
+
+ yyjson_mut_obj_add_str(doc, obj, "position", ((const char*[]) {
+ "left",
+ "top",
+ "right",
+ })[options->position]);
+
+#if FF_HAVE_CHAFA
+ {
+ yyjson_mut_val* chafa = yyjson_mut_obj(doc);
+ yyjson_mut_obj_add_bool(doc, chafa, "fgOnly", options->chafaFgOnly);
+ yyjson_mut_obj_add_strbuf(doc, chafa, "symbols", &options->chafaSymbols);
+ if (options->chafaCanvasMode <= 7) {
+ yyjson_mut_obj_add_str(doc, chafa, "canvasMode", ((const char*[]) {
+ "TRUECOLOR",
+ "INDEXED_256",
+ "INDEXED_240",
+ "INDEXED_16",
+ "FGBG_BGFG",
+ "FGBG",
+ "INDEXED_8",
+ "INDEXED_16_8",
+ })[options->chafaCanvasMode]);
+ }
+ if (options->chafaColorSpace <= 1) {
+ yyjson_mut_obj_add_str(doc, chafa, "colorSpace", ((const char*[]) {
+ "RGB",
+ "DIN99D",
+ })[options->chafaColorSpace]);
+ }
+ if (options->chafaDitherMode <= 2) {
+ yyjson_mut_obj_add_str(doc, chafa, "ditherMode", ((const char*[]) {
+ "NONE",
+ "ORDERED",
+ "DIFFUSION",
+ })[options->chafaDitherMode]);
+ }
+
+ yyjson_mut_obj_add_val(doc, obj, "chafa", chafa);
+ }
+#endif
+
+ yyjson_mut_obj_add_val(doc, doc->root, "logo", obj);
+}
diff --git a/src/options/logo.h b/src/options/logo.h
new file mode 100644
index 0000000..c77c678
--- /dev/null
+++ b/src/options/logo.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include "common/ffdata.h"
+
+#define FASTFETCH_LOGO_MAX_NAMES 9
+#define FASTFETCH_LOGO_MAX_COLORS 9 // two digits would make parsing much more complicated (index 1 - 9)
+
+typedef enum FF_A_PACKED FFLogoType {
+ FF_LOGO_TYPE_AUTO, // if something is given, first try builtin, then file. Otherwise detect logo
+ FF_LOGO_TYPE_BUILTIN, // builtin ascii art
+ FF_LOGO_TYPE_SMALL, // builtin ascii art, small version
+ FF_LOGO_TYPE_FILE, // text file, printed with color code replacement
+ FF_LOGO_TYPE_FILE_RAW, // text file, printed as is
+ FF_LOGO_TYPE_DATA, // text data, printed with color code replacement
+ FF_LOGO_TYPE_DATA_RAW, // text data, printed as is
+ FF_LOGO_TYPE_COMMAND_RAW, // command to generate text data, printed as is
+ FF_LOGO_TYPE_IMAGE_SIXEL, // image file, printed as sixel codes
+ FF_LOGO_TYPE_IMAGE_KITTY, // image file, printed as kitty graphics protocol
+ FF_LOGO_TYPE_IMAGE_KITTY_DIRECT, // image file, tell the terminal emulator to read image data from the specified file (Supported by kitty and wezterm)
+ FF_LOGO_TYPE_IMAGE_KITTY_ICAT, // image file, use `kitten icat` to display the image. Requires binary `kitten` to be installed"
+ FF_LOGO_TYPE_IMAGE_ITERM, // image file, printed as iterm graphics protocol
+ FF_LOGO_TYPE_IMAGE_CHAFA, // image file, printed as ascii art using libchafa
+ FF_LOGO_TYPE_IMAGE_RAW, // image file, printed as raw binary string
+ FF_LOGO_TYPE_NONE, // `--logo none`, but still applies colors to the system information output (unless `--pipe` is set)
+} FFLogoType;
+
+typedef enum FF_A_PACKED FFLogoPosition {
+ FF_LOGO_POSITION_LEFT,
+ FF_LOGO_POSITION_TOP,
+ FF_LOGO_POSITION_RIGHT,
+} FFLogoPosition;
+
+typedef struct FFOptionsLogo {
+ FFstrbuf source;
+ FFLogoType type;
+ FFLogoPosition position;
+ FFstrbuf colors[FASTFETCH_LOGO_MAX_COLORS];
+ uint32_t width;
+ uint32_t height;
+ uint32_t paddingTop;
+ uint32_t paddingLeft;
+ uint32_t paddingRight;
+ uint32_t paddingBottom;
+ bool printRemaining;
+ bool preserveAspectRatio;
+ bool recache;
+
+#if FF_HAVE_CHAFA
+ bool chafaFgOnly;
+ FFstrbuf chafaSymbols;
+ uint32_t chafaCanvasMode;
+ uint32_t chafaColorSpace;
+ uint32_t chafaDitherMode;
+#endif
+} FFOptionsLogo;
+
+void ffOptionsInitLogo(FFOptionsLogo* options);
+bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, const char* value);
+void ffOptionsDestroyLogo(FFOptionsLogo* options);
+const char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* root);
+void ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options);