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/logo/logo.c | 813 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 813 insertions(+) create mode 100644 src/logo/logo.c (limited to 'src/logo/logo.c') diff --git a/src/logo/logo.c b/src/logo/logo.c new file mode 100644 index 0000000..571f1aa --- /dev/null +++ b/src/logo/logo.c @@ -0,0 +1,813 @@ +#include "logo/logo.h" +#include "common/io.h" +#include "common/printing.h" +#include "common/processing.h" +#include "common/textModifier.h" +#include "common/strutil.h" +#include "detection/media/media.h" +#include "detection/os/os.h" +#include "detection/terminalshell/terminalshell.h" + +#include +#include +#include + +typedef struct FFLogoCachedLine { + FFstrbuf chars; + uint32_t width; +} FFLogoCachedLine; + +static void logoLineCacheClear(FFLogoLineCacheState* cache) { + FF_LIST_FOR_EACH (FFLogoCachedLine, line, cache->lines) { + ffStrbufDestroy(&line->chars); + } + ffListDestroy(&cache->lines); + cache->nextLine = 0; + cache->rightOffset = 0; +} + +static void logoLineCachePush(const FFstrbuf* chars, uint32_t width, FFLogoLineCacheState* cache) { + FFLogoCachedLine* line = FF_LIST_ADD(FFLogoCachedLine, cache->lines); + if (width > 0) { + ffStrbufInitCopy(&line->chars, chars); + if (!instance.config.display.pipe) { + ffStrbufAppendS(&line->chars, FASTFETCH_TEXT_MODIFIER_RESET); + } + } else { + ffStrbufInit(&line->chars); + } + line->width = width; +} + +static void logoLineCacheBuild(FFLogoLineCacheState* cache, const char* data, bool doColorReplacement) { + FFOptionsLogo* options = &instance.config.logo; + bool keepCarryColor = options->type != FF_LOGO_TYPE_IMAGE_CHAFA; + + logoLineCacheClear(cache); + + // Overrides the auto detected max-width with the configured width, if set. + // In case we fail to get the actual width of the logo, as we don't use `wcwidth` + uint32_t maxLineWidth = options->width; + uint32_t parsedHeight = 0; + + FF_STRBUF_AUTO_DESTROY carryColor = ffStrbufCreate(); + if (keepCarryColor && doColorReplacement && !instance.config.display.pipe) { + ffStrbufSetF(&carryColor, "\e[%sm", options->colors[0].chars); + } + + for (uint32_t i = 0; i < options->paddingTop; ++i) { + logoLineCachePush(NULL, 0, cache); + } + + if (*data != '\0') { + while (true) { + FF_STRBUF_AUTO_DESTROY line = ffStrbufCreateA(256); + uint32_t lineWidth = 0; + + if (!instance.config.display.pipe && instance.config.display.brightColor) { + ffStrbufAppendS(&line, FASTFETCH_TEXT_MODIFIER_BOLT); + } + + if (keepCarryColor && carryColor.length > 0) { + ffStrbufAppend(&line, &carryColor); + } + + if ((options->position != FF_LOGO_POSITION_RIGHT) && options->paddingLeft > 0) { + ffStrbufAppendNC(&line, options->paddingLeft, ' '); + lineWidth += options->paddingLeft; + } + + while (*data != '\0' && *data != '\n' && !(*data == '\r' && *(data + 1) == '\n')) { + if (*data == '\t') { + ffStrbufAppendNC(&line, 4, ' '); + lineWidth += 4; + ++data; + continue; + } + + if (*data == '\e' && *(data + 1) == '[') { + const char* start = data; + data += 2; + + while (ffCharIsDigit(*data) || *data == ';') { + ++data; + } + + if (isascii(*data)) { + ++data; + + uint32_t escLen = (uint32_t) (data - start); + ffStrbufAppendNS(&line, escLen, start); + + if (keepCarryColor && start[escLen - 1] == 'm') { + ffStrbufSetNS(&carryColor, escLen, start); + } + continue; + } + + lineWidth += (uint32_t) (data - start - 1); + } + + if (doColorReplacement && *data == '$') { + ++data; + + if (*data == '$' || *data == '\0') { + ffStrbufAppendC(&line, '$'); + ++lineWidth; + ++data; + continue; + } + + if (!instance.config.display.pipe) { + int index = *data - '1'; + if (index >= 0 && index < FASTFETCH_LOGO_MAX_COLORS) { + if (keepCarryColor) { + ffStrbufSetF(&carryColor, "\e[%sm", options->colors[index].chars); + ffStrbufAppend(&line, &carryColor); + } else { + ffStrbufAppendF(&line, "\e[%sm", options->colors[index].chars); + } + ++data; + continue; + } + + ffStrbufAppendC(&line, '$'); + ++lineWidth; + } else { + ++data; + continue; + } + } + + uint8_t charWidth; + uint8_t bytes = ffUtf8CharLenWidth(data, UINT32_MAX, &charWidth); + lineWidth += charWidth; + + for (uint8_t i = 0; i < bytes; ++i) { + if (*data == '\0') { + break; + } + + ffStrbufAppendC(&line, *data); + ++data; + } + } + + logoLineCachePush(&line, lineWidth, cache); + if (lineWidth > maxLineWidth) { + maxLineWidth = lineWidth; + } + + if (*data == '\n' || (*data == '\r' && *(data + 1) == '\n')) { + if (*data == '\r') { + ++data; + } + ++data; + ++parsedHeight; + continue; + } + + break; + } + } + + if (options->type != FF_LOGO_TYPE_IMAGE_CHAFA && options->height > parsedHeight) { + parsedHeight = options->height; + } + + instance.state.logoHeight = options->paddingTop + parsedHeight; + if (options->position == FF_LOGO_POSITION_LEFT) { + instance.state.logoWidth = maxLineWidth + options->paddingRight; + } else { + instance.state.logoWidth = 0; + } + + uint32_t totalLines = instance.state.logoHeight + 1; + while (cache->lines.length < totalLines) { + logoLineCachePush(NULL, 0, cache); + } + + cache->nextLine = 0; + cache->rightOffset = maxLineWidth + options->paddingRight - 1; +} + +static bool ffLogoPrintCharsRaw(const char* data, size_t length, bool printError) { + FFOptionsLogo* options = &instance.config.logo; + FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); + + if (!options->width || !options->height) { + if (options->position == FF_LOGO_POSITION_LEFT) { + ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1); + } else if (options->position == FF_LOGO_POSITION_TOP) { + ffStrbufAppendNC(&buf, options->paddingTop, '\n'); + ffStrbufAppendNC(&buf, options->paddingLeft, ' '); + } else if (options->position == FF_LOGO_POSITION_RIGHT) { + if (!options->width) { + if (printError) { + fputs("Logo (image-raw): Must set logo width when using position right\n", stderr); + } + return false; + } + ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;9999999H\e[%uD", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width); + } + ffStrbufAppendNS(&buf, (uint32_t) length, data); + ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); + + if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) { + uint16_t X = 0, Y = 0; + // Windows Terminal doesn't report `\e` for some reason + const char* error = ffGetTerminalResponse("\e[6n", 2, "%*[^0-9]%hu;%huR", &Y, &X); // %*[^0-9]: ignore optional \e[ + if (error) { + if (printError) { + fprintf(stderr, "\nLogo (image-raw): fail to query cursor position: %s\n", error); + } + return true; + } + if (options->position == FF_LOGO_POSITION_LEFT) { + if (options->width + options->paddingLeft > X) { + X = (uint16_t) (options->width + options->paddingLeft); + } + instance.state.logoWidth = X + instance.config.logo.paddingRight - 1; + } + instance.state.logoHeight = Y; + fputs("\e[H", stdout); + } else if (options->position == FF_LOGO_POSITION_TOP) { + instance.state.logoWidth = instance.state.logoHeight = 0; + ffPrintCharTimes('\n', options->paddingRight); + } + } else { + ffStrbufAppendNC(&buf, options->paddingTop, '\n'); + + if (options->position == FF_LOGO_POSITION_RIGHT) { + ffStrbufAppendF(&buf, "\e[9999999C\e[%uD", (unsigned) options->paddingRight + options->width); + } else if (options->paddingLeft) { + ffStrbufAppendF(&buf, "\e[%uC", (unsigned) options->paddingLeft); + } + + ffStrbufAppendNS(&buf, (uint32_t) length, data); + ffStrbufAppendC(&buf, '\n'); + + if (options->position == FF_LOGO_POSITION_LEFT) { + instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight; + instance.state.logoHeight = options->paddingTop + options->height; + ffStrbufAppendF(&buf, "\e[%uA", (unsigned) instance.state.logoHeight); + } else if (options->position == FF_LOGO_POSITION_TOP) { + instance.state.logoWidth = instance.state.logoHeight = 0; + ffStrbufAppendNC(&buf, options->paddingRight, '\n'); + } else if (options->position == FF_LOGO_POSITION_RIGHT) { + instance.state.logoWidth = instance.state.logoHeight = 0; + ffStrbufAppendF(&buf, "\e[%uA", (unsigned) options->height); + } + ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); + } + + return true; +} + +void ffLogoPrintChars(const char* data, bool doColorReplacement) { + FFOptionsLogo* options = &instance.config.logo; + FFLogoLineCacheState* cache = &instance.state.logoLineCache; + + logoLineCacheBuild(cache, data, doColorReplacement); + + if (options->position != FF_LOGO_POSITION_TOP) { + return; + } + + FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(4096); + FF_LIST_FOR_EACH (FFLogoCachedLine, line, cache->lines) { + ffStrbufAppend(&result, &line->chars); + ffStrbufAppendC(&result, '\n'); + } + ffStrbufAppendNC(&result, options->paddingBottom, '\n'); + ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &result); + instance.state.logoWidth = instance.state.logoHeight = 0; + logoLineCacheClear(cache); +} + +static void logoApplyColors(const FFlogo* logo, bool replacement) { + if (instance.config.display.colorTitle.length == 0) { + ffStrbufAppendS(&instance.config.display.colorTitle, logo->colorTitle ?: logo->colors[0]); + } + + if (instance.config.display.colorKeys.length == 0) { + ffStrbufAppendS(&instance.config.display.colorKeys, logo->colorKeys ?: logo->colors[1]); + } + + if (replacement) { + FFOptionsLogo* options = &instance.config.logo; + + const char* const* colors = logo->colors; + for (int i = 0; *colors != NULL && i < FASTFETCH_LOGO_MAX_COLORS; i++, colors++) { + if (options->colors[i].length == 0) { + ffStrbufAppendS(&options->colors[i], *colors); + } + } + } +} + +static bool logoHasName(const FFlogo* logo, const FFstrbuf* name, bool small) { + for ( + const char* const* logoName = logo->names; + *logoName != NULL && logoName <= &logo->names[FASTFETCH_LOGO_MAX_NAMES]; + ++logoName) { + if (small) { + uint32_t logoNameLength = (uint32_t) (strlen(*logoName) - strlen("_small")); + if (name->length == logoNameLength && strncasecmp(*logoName, name->chars, logoNameLength) == 0) { + return true; + } + } + if (ffStrbufIgnCaseEqualS(name, *logoName)) { + return true; + } + } + + return false; +} + +static const FFlogo* logoGetBuiltin(const FFstrbuf* name, FFLogoSize size) { + if (name->length == 0 || !isalpha(name->chars[0])) { + return NULL; + } + + for (const FFlogo* logo = ffLogoBuiltins[toupper(name->chars[0]) - 'A']; *logo->names; ++logo) { + switch (size) { + // Never use alternate logos + case FF_LOGO_SIZE_NORMAL: + if (logo->type != FF_LOGO_LINE_TYPE_NORMAL) { + continue; + } + break; + case FF_LOGO_SIZE_SMALL: + if (logo->type != FF_LOGO_LINE_TYPE_SMALL_BIT) { + continue; + } + break; + default: + break; + } + + if (logoHasName(logo, name, size == FF_LOGO_SIZE_SMALL)) { + return logo; + } + } + + return NULL; +} + +static const FFlogo* logoGetBuiltinDetected(FFLogoSize size) { + const FFOSResult* os = ffDetectOS(); + + const FFlogo* logo = logoGetBuiltin(&os->id, size); + if (logo != NULL) { + return logo; + } + + logo = logoGetBuiltin(&os->name, size); + if (logo != NULL) { + return logo; + } + + if (ffStrbufContainC(&os->idLike, ' ')) { + FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); + for ( + uint32_t start = 0, end = ffStrbufFirstIndexC(&os->idLike, ' '); + true; + start = end + 1, end = ffStrbufNextIndexC(&os->idLike, start, ' ')) { + ffStrbufSetNS(&buf, end - start, os->idLike.chars + start); + logo = logoGetBuiltin(&buf, size); + if (logo != NULL) { + return logo; + } + + if (end >= os->idLike.length) { + break; + } + } + } else { + logo = logoGetBuiltin(&os->idLike, size); + if (logo != NULL) { + return logo; + } + } + + logo = logoGetBuiltin(&instance.state.platform.sysinfo.name, size); + if (logo != NULL) { + return logo; + } + + return &ffLogoUnknown; +} + +static void logoPrintStruct(const FFlogo* logo) { + logoApplyColors(logo, true); + + ffLogoPrintChars(logo->lines, true); +} + +static void logoPrintNone(void) { + if (!instance.config.display.pipe) { + logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false); + } + instance.state.logoHeight = 0; + instance.state.logoWidth = 0; +} + +static bool logoPrintBuiltinIfExists(const FFstrbuf* name, FFLogoSize size) { + if (name->chars[0] == '~' || name->chars[0] == '.' || name->chars[0] == '/' +#if _WIN32 + || (ffCharIsEnglishAlphabet(name->chars[0]) && name->chars[1] == ':') // Windows drive letter +#endif + ) + return false; // Paths + + if (ffStrbufIgnCaseEqualS(name, "none")) { + logoPrintNone(); + return true; + } + + const FFlogo* logo = ffLogoGetBuiltinForName(name, size); + if (logo == NULL) { + return false; + } + + logoPrintStruct(logo); + + return true; +} + +void ffLogoPrintDetected(FFLogoSize size) { + logoPrintStruct(logoGetBuiltinDetected(size)); +} + +static bool logoPrintData(bool doColorReplacement, FFstrbuf* source) { + if (source->length == 0) { + return false; + } + + logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement); + ffLogoPrintChars(source->chars, doColorReplacement); + return true; +} + +static bool updateLogoPath(void) { + FFOptionsLogo* options = &instance.config.logo; + + if (ffPathExists(options->source.chars, FF_PATHTYPE_FILE)) { + return true; + } + + if (ffStrbufEqualS(&options->source, "-")) { // stdin + return true; + } + +#if !FF_MODULE_DISABLE_MEDIA + if (ffStrbufIgnCaseEqualS(&options->source, "media-cover")) { + const FFMediaResult* media = ffDetectMedia(true); + if (media->cover.length == 0) { + return false; + } + ffStrbufSet(&options->source, &media->cover); + return true; + } +#endif + + FF_STRBUF_AUTO_DESTROY fullPath = ffStrbufCreateA(128); + if (ffPathExpandEnv(options->source.chars, &fullPath) && ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) { + ffStrbufDestroy(&options->source); + ffStrbufInitMove(&options->source, &fullPath); + return true; + } + + FF_LIST_FOR_EACH (FFstrbuf, dataDir, instance.state.platform.dataDirs) { + // We need to copy it, because multiple threads might be using dataDirs at the same time + ffStrbufSet(&fullPath, dataDir); + ffStrbufAppendS(&fullPath, "fastfetch/logos/"); + ffStrbufAppend(&fullPath, &options->source); + + if (ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) { + ffStrbufDestroy(&options->source); + ffStrbufInitMove(&options->source, &fullPath); + return true; + } + } + + return false; +} + +static bool logoPrintFileIfExists(bool doColorReplacement, bool raw) { + FFOptionsLogo* options = &instance.config.logo; + + FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); + + if (ffStrbufEqualS(&options->source, "-") + ? !ffAppendFDBuffer(FFUnixFD2NativeFD(STDIN_FILENO), &content) + : !ffAppendFileBuffer(options->source.chars, &content)) { + if (instance.config.display.showErrors) { + fprintf(stderr, "Logo: Failed to load file content from logo source: %s\n", options->source.chars); + } + return false; + } + + logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement); + if (raw) { + return ffLogoPrintCharsRaw(content.chars, content.length, instance.config.display.showErrors); + } + + ffLogoPrintChars(content.chars, doColorReplacement); + return true; +} + +static bool logoPrintImageIfExists(FFLogoType logo, bool printError) { + if (!ffLogoPrintImageIfExists(logo, printError)) { + return false; + } + + logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false); + return true; +} + +static bool logoTryKnownType(void) { + FFOptionsLogo* options = &instance.config.logo; + + if (options->type == FF_LOGO_TYPE_NONE) { + logoPrintNone(); + return true; + } + + if (options->type == FF_LOGO_TYPE_BUILTIN) { + return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN); + } + + if (options->type == FF_LOGO_TYPE_SMALL) { + return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_SMALL); + } + + if (options->type == FF_LOGO_TYPE_DATA) { + return logoPrintData(true, &options->source); + } + + if (options->type == FF_LOGO_TYPE_DATA_RAW) { + return logoPrintData(false, &options->source); + } + + if (options->type == FF_LOGO_TYPE_COMMAND_RAW) { + FF_STRBUF_AUTO_DESTROY source = ffStrbufCreate(); + + const char* error = ffProcessAppendStdOut(&source, (char* const[]){ +#ifdef _WIN32 + "cmd.exe", "/c", +#else + "/bin/sh", "-c", +#endif + options->source.chars, + NULL }); + + if (error) { + if (instance.config.display.showErrors) { + fprintf(stderr, "Logo: failed to execute command `%s`: %s\n", options->source.chars, error); + } + return false; + } + + return logoPrintData(false, &source); + } + + // We sure have a file, resolve relative paths + if (!updateLogoPath()) { + if (instance.config.display.showErrors) { + fprintf(stderr, "Logo: Failed to resolve logo source: %s\n", options->source.chars); + } + return false; + } + + if (options->type == FF_LOGO_TYPE_FILE) { + return logoPrintFileIfExists(true, false); + } + + if (options->type == FF_LOGO_TYPE_FILE_RAW) { + return logoPrintFileIfExists(false, false); + } + + if (options->type == FF_LOGO_TYPE_IMAGE_RAW) { + return logoPrintFileIfExists(false, true); + } + + return logoPrintImageIfExists(options->type, instance.config.display.showErrors); +} + +void ffLogoPrint(void) { + const FFOptionsLogo* options = &instance.config.logo; + + if (options->type == FF_LOGO_TYPE_NONE) { + logoPrintNone(); + return; + } + + // If the source is not set, we can directly print the detected logo. + if (options->source.length == 0) { + ffLogoPrintDetected(options->type == FF_LOGO_TYPE_SMALL ? FF_LOGO_SIZE_SMALL : FF_LOGO_SIZE_NORMAL); + return; + } + + // If the source and source type is set to something else than auto, always print with the set type. + if (options->source.length > 0 && options->type != FF_LOGO_TYPE_AUTO) { + if (!logoTryKnownType()) { + if (instance.config.display.showErrors) { + // Image logo should have been handled + if (options->type == FF_LOGO_TYPE_BUILTIN || options->type == FF_LOGO_TYPE_SMALL) { + fprintf(stderr, "Logo: Failed to load %s logo: %s\n", options->type == FF_LOGO_TYPE_BUILTIN ? "builtin" : "builtin small", options->source.chars); + } + } + + ffLogoPrintDetected(FF_LOGO_SIZE_UNKNOWN); + } + return; + } + + // If source matches the name of a builtin logo, print it and return. + if (logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN)) { + return; + } + + // Make sure the logo path is set correctly. + if (updateLogoPath()) { + if (ffStrbufEndsWithIgnCaseS(&options->source, ".raw")) { + if (logoPrintFileIfExists(false, true)) { + return; + } + } + + if (!ffStrbufEndsWithIgnCaseS(&options->source, ".txt")) { +#if !FF_MODULE_DISABLE_TERMINAL + const FFTerminalResult* terminal = ffDetectTerminal(); + + bool supportsIterm2 = ffStrbufEqualS(&terminal->prettyName, "iTerm"); + + if (supportsIterm2 && logoPrintImageIfExists(FF_LOGO_TYPE_IMAGE_ITERM, false)) { + return; + } + + // Terminal emulators that support kitty graphics protocol. + bool supportsKitty = + ffStrbufIgnCaseEqualS(&terminal->processName, "kitty") || + ffStrbufIgnCaseEqualS(&terminal->processName, "konsole") || + ffStrbufIgnCaseEqualS(&terminal->processName, "wezterm") || + ffStrbufIgnCaseEqualS(&terminal->processName, "wayst") || + ffStrbufIgnCaseEqualS(&terminal->processName, "ghostty") || + #ifdef __APPLE__ + ffStrbufIgnCaseEqualS(&terminal->processName, "WarpTerminal") || + #else + ffStrbufIgnCaseEqualS(&terminal->processName, "warp") || + #endif + false; +#else + bool supportsKitty = false; +#endif + + // Try to load the logo as an image. If it succeeds, print it and return. + if (logoPrintImageIfExists(supportsKitty ? FF_LOGO_TYPE_IMAGE_KITTY : FF_LOGO_TYPE_IMAGE_CHAFA, false)) { + return; + } + } + + // Try to load the logo as a file. If it succeeds, print it and return. + if (logoPrintFileIfExists(true, false)) { + return; + } + } else { + if (instance.config.display.showErrors) { + fprintf(stderr, "Logo: Failed to resolve logo source: %s\n", options->source.chars); + } + } + + ffLogoPrintDetected(FF_LOGO_SIZE_UNKNOWN); +} + +void ffLogoPrintLine(void) { + FFLogoLineCacheState* cache = &instance.state.logoLineCache; + FFOptionsLogo* logo = &instance.config.logo; + + if (cache->lines.length > 0) { + // Line cache is enabled. Always move cursor with whitespaces to make lolcat happy + if (cache->nextLine < cache->lines.length) { + // Print logo line and move cursor + FFLogoCachedLine* line = FF_LIST_GET(FFLogoCachedLine, cache->lines, cache->nextLine); + + if (logo->position == FF_LOGO_POSITION_RIGHT) { + printf("\033[9999999C\033[%uD", cache->rightOffset); + ffStrbufWriteTo(&line->chars, stdout); + + fputs("\033[G", stdout); + } else { + ffStrbufWriteTo(&line->chars, stdout); + + uint32_t remaining = instance.state.logoWidth; + remaining = line->width < remaining ? remaining - line->width : 0; + ffPrintCharTimes(' ', remaining); + } + ++cache->nextLine; + } else if (logo->position == FF_LOGO_POSITION_LEFT) { + // Move cursor to the start position + ffPrintCharTimes(' ', instance.state.logoWidth); + } + } else if (instance.state.logoWidth > 0) { + printf("\033[%uC", instance.state.logoWidth); + } + + if (instance.state.dynamicInterval > 0 && logo->position == FF_LOGO_POSITION_LEFT) { + fputs("\033[K", stdout); // Clear to the end of the line + // Note that we don't clear the line when the logo is on the right, as it will also clear the logo itself + } + + ++instance.state.keysHeight; +} + +void ffLogoPrintRemaining(void) { + FFLogoLineCacheState* cache = &instance.state.logoLineCache; + FFOptionsLogo* logo = &instance.config.logo; + + if (cache->lines.length > 0 && (logo->position == FF_LOGO_POSITION_LEFT || logo->position == FF_LOGO_POSITION_RIGHT)) { + while (cache->nextLine < cache->lines.length) { + FFLogoCachedLine* line = FF_LIST_GET(FFLogoCachedLine, cache->lines, cache->nextLine); + + if (logo->position == FF_LOGO_POSITION_RIGHT) { + printf("\033[9999999C\033[%uD", cache->rightOffset); + } + ffStrbufPutTo(&line->chars, stdout); + + ++cache->nextLine; + } + + if (!instance.config.display.pipe) { + fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout); + } + + instance.state.keysHeight = instance.state.logoHeight + 1; + logoLineCacheClear(cache); + return; + } + + if (instance.state.keysHeight <= instance.state.logoHeight) { + ffPrintCharTimes('\n', instance.state.logoHeight - instance.state.keysHeight + 1); + } + instance.state.keysHeight = instance.state.logoHeight + 1; +} + +void ffLogoBuiltinPrint(void) { + FFOptionsLogo* options = &instance.config.logo; + options->position = FF_LOGO_POSITION_TOP; + options->paddingRight = 2; // empty line after logo printing + FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); + + for (uint8_t ch = 0; ch < 26; ++ch) { + for (const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) { + if (instance.config.display.pipe) { + ffStrbufSetF(&buf, "%s:\n", logo->names[0]); + } else { + ffStrbufSetF(&buf, "\e[%sm%s:\e[0m\n", logo->colors[0], logo->names[0]); + } + ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); + logoPrintStruct(logo); + + for (uint8_t i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) { + ffStrbufClear(&options->colors[i]); + } + } + } +} + +void ffLogoBuiltinList(void) { + uint32_t counter = 0; + for (uint8_t ch = 0; ch < 26; ++ch) { + for (const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) { + ++counter; + printf("%u)%s ", counter, counter < 10 ? " " : ""); + + for ( + const char* const* names = logo->names; + *names != NULL && names <= &logo->names[FASTFETCH_LOGO_MAX_NAMES]; + ++names) { + printf("\"%s\" ", *names); + } + + putchar('\n'); + } + } +} + +void ffLogoBuiltinListAutocompletion(void) { + for (uint8_t ch = 0; ch < 26; ++ch) { + for (const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) { + printf("%s\n", logo->names[0]); + } + } +} + +const FFlogo* ffLogoGetBuiltinForName(const FFstrbuf* name, FFLogoSize size) { + return ffStrbufEqualS(name, "?") ? &ffLogoUnknown : logoGetBuiltin(name, size); +} + +const FFlogo* ffLogoGetBuiltinDetected(FFLogoSize size) { + return logoGetBuiltinDetected(size); +} -- cgit v1.2.3