From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/options/display.c | 1168 +++++++++++++++++++++++++++++++++++++++++++++++++ src/options/display.h | 97 ++++ src/options/general.c | 134 ++++++ src/options/general.h | 27 ++ src/options/logo.c | 562 ++++++++++++++++++++++++ src/options/logo.h | 61 +++ 6 files changed, 2049 insertions(+) create mode 100644 src/options/display.c create mode 100644 src/options/display.h create mode 100644 src/options/general.c create mode 100644 src/options/general.h create mode 100644 src/options/logo.c create mode 100644 src/options/logo.h (limited to 'src/options') 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 + +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 \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 + +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 \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 \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); -- cgit v1.2.3