#include "fastfetch.h" #include "common/percent.h" #include "common/color.h" #include "common/option.h" #include "common/jsonconfig.h" #include "common/textModifier.h" #include "common/strutil.h" static void appendOutputColor(FFstrbuf* buffer, const FFModuleArgs* module) { if (module->outputColor.length) { ffStrbufAppendF(buffer, "\e[%sm", module->outputColor.chars); } else if (instance.config.display.colorOutput.length) { ffStrbufAppendF(buffer, "\e[%sm", instance.config.display.colorOutput.chars); } } const char* ffPercentParseTypeJsonConfig(yyjson_val* jsonVal, FFPercentageTypeFlags* result) { if (yyjson_is_uint(jsonVal)) { *result = (FFPercentageTypeFlags) yyjson_get_uint(jsonVal); return NULL; } if (yyjson_is_arr(jsonVal)) { FFPercentageTypeFlags flags = 0; yyjson_val* item; size_t idx, max; yyjson_arr_foreach (jsonVal, idx, max, item) { const char* flag = yyjson_get_str(item); if (!flag) { return "Error: percent.type: invalid flag string"; } if (ffStrEqualsIgnCase(flag, "num")) { flags |= FF_PERCENTAGE_TYPE_NUM_BIT; } else if (ffStrEqualsIgnCase(flag, "bar")) { flags |= FF_PERCENTAGE_TYPE_BAR_BIT; } else if (ffStrEqualsIgnCase(flag, "hide-others")) { flags |= FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT; } else if (ffStrEqualsIgnCase(flag, "num-color")) { flags |= FF_PERCENTAGE_TYPE_NUM_COLOR_BIT; } else if (ffStrEqualsIgnCase(flag, "bar-monochrome")) { flags |= FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT; } else { return "Error: percent.type: unknown flag string"; } } *result = flags; return NULL; } return "Error: usage: percent.type must be a number or an array of strings"; } void ffPercentAppendBar(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, const FFModuleArgs* module) { uint8_t green = config.green, yellow = config.yellow; assert(green <= 100 && yellow <= 100); const FFOptionsDisplay* options = &instance.config.display; const bool borderAsValue = options->barBorderLeftElapsed.length && options->barBorderRightElapsed.length; if (!borderAsValue && options->barBorderLeft.length) { if (!options->pipe && options->barColorBorder.length > 0) { ffStrbufAppendF(buffer, "\e[%sm", options->barColorBorder.chars); } ffStrbufAppend(buffer, &options->barBorderLeft); } if (percent == -DBL_MAX) { // Use total color for simplification if (!options->pipe && options->barColorTotal.length > 0) { ffStrbufAppendS(buffer, "\e[" FF_COLOR_FG_LIGHT_BLACK "m"); } for (uint8_t i = 0; i < options->barWidth; ++i) { ffStrbufAppend(buffer, borderAsValue && i == 0 ? &options->barBorderLeft : borderAsValue && i == options->barWidth - 1 ? &options->barBorderRight : &options->barCharTotal); } } else { const char* colorGreen = options->percentColorGreen.chars; const char* colorYellow = options->percentColorYellow.chars; const char* colorRed = options->percentColorRed.chars; FFPercentageTypeFlags percentType = config.type == 0 ? options->percentType : config.type; uint8_t blocksPercent = (uint8_t) (percent / 100.0 * options->barWidth + 0.5); assert(blocksPercent <= options->barWidth); bool autoColorElapsed = ffStrbufIgnCaseEqualS(&options->barColorElapsed, "auto"); bool monochrome = (percentType & FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT) || !autoColorElapsed; if (!options->pipe && options->barColorElapsed.length > 0 && monochrome) { const char* color = NULL; if (!autoColorElapsed) { color = options->barColorElapsed.chars; } else if (green <= yellow) { if (percent < green) { color = colorGreen; } else if (percent < yellow) { color = colorYellow; } else { color = colorRed; } } else { if (percent < yellow) { color = colorRed; } else if (percent < green) { color = colorYellow; } else { color = colorGreen; } } ffStrbufAppendF(buffer, "\e[%sm", color); } for (uint8_t i = 0; i < blocksPercent; ++i) { if (!options->pipe && options->barColorElapsed.length > 0 && !monochrome) { uint32_t section1Begin = (uint32_t) ((green <= yellow ? green : yellow) / 100.0 * options->barWidth + 0.5); uint32_t section2Begin = (uint32_t) ((green > yellow ? green : yellow) / 100.0 * options->barWidth + 0.5); if (i == section2Begin) { ffStrbufAppendF(buffer, "\e[%sm", (green > yellow ? colorGreen : colorRed)); } else if (i == section1Begin) { ffStrbufAppendF(buffer, "\e[%sm", colorYellow); } else if (i == 0) { ffStrbufAppendF(buffer, "\e[%sm", (green <= yellow ? colorGreen : colorRed)); } } ffStrbufAppend(buffer, borderAsValue && i == 0 ? &options->barBorderLeftElapsed : borderAsValue && i == options->barWidth - 1 ? &options->barBorderRightElapsed : &options->barCharElapsed); } if (blocksPercent < options->barWidth) { if (!options->pipe && options->barColorTotal.length > 0) { ffStrbufAppendF(buffer, "\e[%sm", options->barColorTotal.chars); } for (uint8_t i = blocksPercent; i < options->barWidth; ++i) { ffStrbufAppend(buffer, borderAsValue && i == 0 ? &options->barBorderLeft : borderAsValue && i == options->barWidth - 1 ? &options->barBorderRight : &options->barCharTotal); } } } if (!borderAsValue && options->barBorderRight.length) { if (!options->pipe && options->barColorBorder.length > 0) { ffStrbufAppendF(buffer, "\e[%sm", options->barColorBorder.chars); } ffStrbufAppend(buffer, &options->barBorderRight); } if (!options->pipe && (options->barColorElapsed.length > 0 || options->barColorTotal.length > 0 || options->barColorBorder.length > 0)) { ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET); appendOutputColor(buffer, module); } } void ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, bool parentheses, const FFModuleArgs* module) { uint8_t green = config.green, yellow = config.yellow; assert(green <= 100 && yellow <= 100); const FFOptionsDisplay* options = &instance.config.display; FFPercentageTypeFlags percentType = config.type == 0 ? options->percentType : config.type; bool colored = !!(percentType & FF_PERCENTAGE_TYPE_NUM_COLOR_BIT); if (parentheses) { ffStrbufAppendC(buffer, '('); } if (colored && !options->pipe) { const char* colorGreen = options->percentColorGreen.chars; const char* colorYellow = options->percentColorYellow.chars; const char* colorRed = options->percentColorRed.chars; if (percent == -DBL_MAX) { ffStrbufAppendS(buffer, "\e[" FF_COLOR_FG_LIGHT_BLACK "m"); } else if (green <= yellow) { if (percent > yellow) { ffStrbufAppendF(buffer, "\e[%sm", colorRed); } else if (percent > green) { ffStrbufAppendF(buffer, "\e[%sm", colorYellow); } else { ffStrbufAppendF(buffer, "\e[%sm", colorGreen); } } else { if (percent < yellow) { ffStrbufAppendF(buffer, "\e[%sm", colorRed); } else if (percent < green) { ffStrbufAppendF(buffer, "\e[%sm", colorYellow); } else { ffStrbufAppendF(buffer, "\e[%sm", colorGreen); } } } if (percent == -DBL_MAX) { ffStrbufAppendS(buffer, "-"); } else { ffStrbufAppendF(buffer, "%*.*f%s%%", options->percentWidth, options->percentNdigits, percent, options->percentSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""); } if (colored && !options->pipe) { ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET); appendOutputColor(buffer, module); } if (parentheses) { ffStrbufAppendC(buffer, ')'); } } bool ffPercentParseCommandOptions(const char* key, const char* subkey, const char* value, FFPercentageModuleConfig* config) { if (!ffStrStartsWithIgnCase(subkey, "percent-")) { return false; } subkey += strlen("percent-"); if (ffStrEqualsIgnCase(subkey, "green")) { uint32_t num = ffOptionParseUInt32(key, value); if (num > 100) { fprintf(stderr, "Error: usage: %s must be between 0 and 100\n", key); exit(480); } config->green = (uint8_t) num; return true; } if (ffStrEqualsIgnCase(subkey, "yellow")) { uint32_t num = ffOptionParseUInt32(key, value); if (num > 100) { fprintf(stderr, "Error: usage: %s must be between 0 and 100\n", key); exit(480); } config->yellow = (uint8_t) num; return true; } if (ffStrEqualsIgnCase(subkey, "type")) { config->type = (FFPercentageTypeFlags) ffOptionParseUInt32(key, value); return true; } return false; } bool ffPercentParseJsonObject(yyjson_val* key, yyjson_val* value, FFPercentageModuleConfig* config) { assert(key); if (!unsafe_yyjson_equals_str(key, "percent")) { return false; } if (!yyjson_is_obj(value)) { fprintf(stderr, "Error: usage: %s must be an object\n", unsafe_yyjson_get_str(key)); exit(480); } yyjson_val* greenVal = yyjson_obj_get(value, "green"); if (greenVal) { int num = yyjson_get_int(greenVal); if (num < 0 || num > 100) { fputs("Error: usage: percent.green must be between 0 and 100\n", stderr); exit(480); } config->green = (uint8_t) num; } yyjson_val* yellowVal = yyjson_obj_get(value, "yellow"); if (yellowVal) { int num = yyjson_get_int(yellowVal); if (num < 0 || num > 100) { fputs("Error: usage: percent.yellow must be between 0 and 100\n", stderr); exit(480); } config->yellow = (uint8_t) num; } yyjson_val* typeVal = yyjson_obj_get(value, "type"); if (typeVal) { const char* error = ffPercentParseTypeJsonConfig(typeVal, &config->type); if (error) { fputs(error, stderr); exit(480); } } return true; } void ffPercentGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFPercentageModuleConfig config) { yyjson_mut_val* percent = yyjson_mut_obj_add_obj(doc, module, "percent"); yyjson_mut_obj_add_uint(doc, percent, "green", config.green); yyjson_mut_obj_add_uint(doc, percent, "yellow", config.yellow); yyjson_mut_obj_add_uint(doc, percent, "type", config.type); }