Files
fastfetch/src/logo/logo.c
T

828 lines
28 KiB
C
Raw Normal View History

#include "logo/logo.h"
2026-01-06 09:48:38 +08:00
#include "common/io.h"
#include "common/printing.h"
#include "common/processing.h"
#include "common/textModifier.h"
2026-05-28 15:58:08 +08:00
#include "common/strutil.h"
#include "detection/media/media.h"
2022-09-04 13:33:18 +02:00
#include "detection/os/os.h"
2022-10-08 16:04:55 +08:00
#include "detection/terminalshell/terminalshell.h"
2022-04-02 14:35:43 +02:00
#include <ctype.h>
#include <stdlib.h>
2022-06-08 10:59:33 +02:00
#include <string.h>
2022-04-02 14:35:43 +02:00
2026-05-22 16:47:55 +08:00
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);
}
2026-05-22 16:47:55 +08:00
} else {
ffStrbufInit(&line->chars);
}
line->width = width;
}
static void logoLineCacheBuild(FFLogoLineCacheState* cache, const char* data, bool doColorReplacement) {
2026-05-22 16:47:55 +08:00
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;
2026-05-22 16:47:55 +08:00
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) {
2026-07-10 18:08:04 +08:00
logoLineCachePush(nullptr, 0, cache);
2026-05-22 16:47:55 +08:00
}
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) {
2026-05-22 16:47:55 +08:00
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;
2026-05-22 16:47:55 +08:00
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) {
2026-05-22 16:47:55 +08:00
instance.state.logoWidth = maxLineWidth + options->paddingRight;
} else {
2026-05-22 16:47:55 +08:00
instance.state.logoWidth = 0;
}
uint32_t totalLines = instance.state.logoHeight + 1;
while (cache->lines.length < totalLines) {
2026-07-10 18:08:04 +08:00
logoLineCachePush(nullptr, 0, cache);
2026-05-22 16:47:55 +08:00
}
cache->nextLine = 0;
cache->rightOffset = maxLineWidth + options->paddingRight - 1;
}
2026-03-29 09:28:49 +08:00
static bool ffLogoPrintCharsRaw(const char* data, size_t length, bool printError) {
2023-10-25 14:43:46 +08:00
FFOptionsLogo* options = &instance.config.logo;
FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
2023-08-04 16:05:25 +08:00
2026-03-29 09:28:49 +08:00
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, ' ');
2026-03-29 09:28:49 +08:00
} else if (options->position == FF_LOGO_POSITION_RIGHT) {
if (!options->width) {
if (printError) {
2024-09-04 10:57:25 +08:00
fputs("Logo (image-raw): Must set logo width when using position right\n", stderr);
2026-03-29 09:28:49 +08:00
}
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);
2026-03-29 09:28:49 +08:00
if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) {
uint16_t X = 0, Y = 0;
2024-09-04 10:57:25 +08:00
// 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[
2026-03-29 09:28:49 +08:00
if (error) {
if (printError) {
fprintf(stderr, "\nLogo (image-raw): fail to query cursor position: %s\n", error);
2026-03-29 09:28:49 +08:00
}
return true;
}
2026-03-29 09:28:49 +08:00
if (options->position == FF_LOGO_POSITION_LEFT) {
if (options->width + options->paddingLeft > X) {
2024-09-04 10:57:25 +08:00
X = (uint16_t) (options->width + options->paddingLeft);
2026-03-29 09:28:49 +08:00
}
instance.state.logoWidth = X + instance.config.logo.paddingRight - 1;
2024-09-04 10:57:25 +08:00
}
instance.state.logoHeight = Y;
fputs("\e[H", stdout);
2026-03-29 09:28:49 +08:00
} else if (options->position == FF_LOGO_POSITION_TOP) {
instance.state.logoWidth = instance.state.logoHeight = 0;
ffPrintCharTimes('\n', options->paddingRight);
}
2026-03-29 09:28:49 +08:00
} else {
2023-09-15 10:53:09 +08:00
ffStrbufAppendNC(&buf, options->paddingTop, '\n');
2026-03-29 09:28:49 +08:00
if (options->position == FF_LOGO_POSITION_RIGHT) {
ffStrbufAppendF(&buf, "\e[9999999C\e[%uD", (unsigned) options->paddingRight + options->width);
2026-03-29 09:28:49 +08:00
} else if (options->paddingLeft) {
ffStrbufAppendF(&buf, "\e[%uC", (unsigned) options->paddingLeft);
2026-03-29 09:28:49 +08:00
}
ffStrbufAppendNS(&buf, (uint32_t) length, data);
ffStrbufAppendC(&buf, '\n');
2026-03-29 09:28:49 +08:00
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);
2026-03-29 09:28:49 +08:00
} else if (options->position == FF_LOGO_POSITION_TOP) {
instance.state.logoWidth = instance.state.logoHeight = 0;
ffStrbufAppendNC(&buf, options->paddingRight, '\n');
2026-03-29 09:28:49 +08:00
} 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;
2023-01-01 12:37:39 +08:00
}
2026-03-29 09:28:49 +08:00
void ffLogoPrintChars(const char* data, bool doColorReplacement) {
FFOptionsLogo* options = &instance.config.logo;
FFLogoLineCacheState* cache = &instance.state.logoLineCache;
logoLineCacheBuild(cache, data, doColorReplacement);
2022-04-02 14:35:43 +02:00
if (options->position != FF_LOGO_POSITION_TOP) {
return;
2026-03-29 09:28:49 +08:00
}
2022-04-02 14:35:43 +02:00
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(4096);
FF_LIST_FOR_EACH (FFLogoCachedLine, line, cache->lines) {
ffStrbufAppend(&result, &line->chars);
ffStrbufAppendC(&result, '\n');
2023-09-03 22:20:11 +08:00
}
ffStrbufAppendNC(&result, options->paddingBottom, '\n');
ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &result);
instance.state.logoWidth = instance.state.logoHeight = 0;
logoLineCacheClear(cache);
2022-04-02 14:35:43 +02:00
}
2026-03-29 09:28:49 +08:00
static void logoApplyColors(const FFlogo* logo, bool replacement) {
if (instance.config.display.colorTitle.length == 0) {
2026-09-13 12:27:17 +08:00
ffStrbufSetStatic(&instance.config.display.colorTitle, logo->colorTitle ?: logo->colors[0]);
2026-03-29 09:28:49 +08:00
}
2026-03-29 09:28:49 +08:00
if (instance.config.display.colorKeys.length == 0) {
2026-09-13 12:27:17 +08:00
ffStrbufSetStatic(&instance.config.display.colorKeys, logo->colorKeys ?: logo->colors[1]);
2026-03-29 09:28:49 +08:00
}
2026-03-29 09:28:49 +08:00
if (replacement) {
FFOptionsLogo* options = &instance.config.logo;
const char* const* colors = logo->colors;
2026-07-10 18:08:04 +08:00
for (int i = 0; *colors != nullptr && i < FASTFETCH_LOGO_MAX_COLORS; i++, colors++) {
2026-03-29 09:28:49 +08:00
if (options->colors[i].length == 0) {
2026-09-13 12:27:17 +08:00
ffStrbufSetStatic(&options->colors[i], *colors);
2026-03-29 09:28:49 +08:00
}
}
}
2022-06-08 10:59:33 +02:00
}
2026-03-29 09:28:49 +08:00
static bool logoHasName(const FFlogo* logo, const FFstrbuf* name, bool small) {
for (
2023-08-03 10:29:50 +08:00
const char* const* logoName = logo->names;
2026-07-10 18:08:04 +08:00
*logoName != nullptr && logoName <= &logo->names[FASTFETCH_LOGO_MAX_NAMES];
2026-03-29 09:28:49 +08:00
++logoName) {
if (small) {
2023-07-06 23:16:05 +08:00
uint32_t logoNameLength = (uint32_t) (strlen(*logoName) - strlen("_small"));
2026-03-29 09:28:49 +08:00
if (name->length == logoNameLength && strncasecmp(*logoName, name->chars, logoNameLength) == 0) {
return true;
}
2023-07-06 23:16:05 +08:00
}
2026-03-29 09:28:49 +08:00
if (ffStrbufIgnCaseEqualS(name, *logoName)) {
2022-06-08 10:59:33 +02:00
return true;
2026-03-29 09:28:49 +08:00
}
2022-06-08 10:59:33 +02:00
}
2022-04-02 14:35:43 +02:00
2022-06-08 10:59:33 +02:00
return false;
2022-04-02 14:35:43 +02:00
}
2026-03-29 09:28:49 +08:00
static const FFlogo* logoGetBuiltin(const FFstrbuf* name, FFLogoSize size) {
if (name->length == 0 || !isalpha(name->chars[0])) {
2026-07-10 18:08:04 +08:00
return nullptr;
2026-03-29 09:28:49 +08:00
}
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
for (const FFlogo* logo = ffLogoBuiltins[toupper(name->chars[0]) - 'A']; *logo->names; ++logo) {
switch (size) {
2023-08-10 16:19:03 +08:00
// Never use alternate logos
2023-07-06 23:16:05 +08:00
case FF_LOGO_SIZE_NORMAL:
2026-03-29 09:28:49 +08:00
if (logo->type != FF_LOGO_LINE_TYPE_NORMAL) {
continue;
}
2023-07-06 23:16:05 +08:00
break;
case FF_LOGO_SIZE_SMALL:
2026-03-29 09:28:49 +08:00
if (logo->type != FF_LOGO_LINE_TYPE_SMALL_BIT) {
continue;
}
2023-07-06 23:16:05 +08:00
break;
default:
break;
}
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
if (logoHasName(logo, name, size == FF_LOGO_SIZE_SMALL)) {
2023-07-06 23:16:05 +08:00
return logo;
2026-03-29 09:28:49 +08:00
}
2022-04-29 21:52:42 +02:00
}
2022-04-29 21:44:32 +02:00
2026-07-10 18:08:04 +08:00
return nullptr;
2022-06-08 10:59:33 +02:00
}
2022-04-29 21:44:32 +02:00
2026-03-29 09:28:49 +08:00
static const FFlogo* logoGetBuiltinDetected(FFLogoSize size) {
const FFOSResult* os = ffDetectOS();
2022-04-29 21:44:32 +02:00
2023-07-06 23:16:05 +08:00
const FFlogo* logo = logoGetBuiltin(&os->id, size);
2026-07-10 18:08:04 +08:00
if (logo != nullptr) {
2022-06-08 10:59:33 +02:00
return logo;
2026-03-29 09:28:49 +08:00
}
2022-04-29 21:44:32 +02:00
2023-07-06 23:16:05 +08:00
logo = logoGetBuiltin(&os->name, size);
2026-07-10 18:08:04 +08:00
if (logo != nullptr) {
2022-06-08 10:59:33 +02:00
return logo;
2026-03-29 09:28:49 +08:00
}
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
if (ffStrbufContainC(&os->idLike, ' ')) {
2025-02-07 10:57:30 +08:00
FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
for (
uint32_t start = 0, end = ffStrbufFirstIndexC(&os->idLike, ' ');
true;
2026-03-29 09:28:49 +08:00
start = end + 1, end = ffStrbufNextIndexC(&os->idLike, start, ' ')) {
2025-02-07 10:57:30 +08:00
ffStrbufSetNS(&buf, end - start, os->idLike.chars + start);
logo = logoGetBuiltin(&buf, size);
2026-07-10 18:08:04 +08:00
if (logo != nullptr) {
2025-02-07 10:57:30 +08:00
return logo;
2026-03-29 09:28:49 +08:00
}
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
if (end >= os->idLike.length) {
2025-02-07 10:57:30 +08:00
break;
2026-03-29 09:28:49 +08:00
}
2025-02-07 10:57:30 +08:00
}
2026-03-29 09:28:49 +08:00
} else {
2025-02-07 10:57:30 +08:00
logo = logoGetBuiltin(&os->idLike, size);
2026-07-10 18:08:04 +08:00
if (logo != nullptr) {
2025-02-07 10:57:30 +08:00
return logo;
2026-03-29 09:28:49 +08:00
}
2025-02-07 10:57:30 +08:00
}
2022-06-08 10:59:33 +02:00
2024-07-02 10:12:26 +08:00
logo = logoGetBuiltin(&instance.state.platform.sysinfo.name, size);
2026-07-10 18:08:04 +08:00
if (logo != nullptr) {
2022-06-08 10:59:33 +02:00
return logo;
2026-03-29 09:28:49 +08:00
}
2022-06-08 10:59:33 +02:00
return &ffLogoUnknown;
2022-06-08 10:59:33 +02:00
}
2026-03-29 09:28:49 +08:00
static void logoPrintStruct(const FFlogo* logo) {
logoApplyColors(logo, true);
2022-06-08 10:59:33 +02:00
2023-08-03 10:29:50 +08:00
ffLogoPrintChars(logo->lines, true);
2022-06-08 10:59:33 +02:00
}
2026-03-29 09:28:49 +08:00
static void logoPrintNone(void) {
if (!instance.config.display.pipe) {
logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);
2026-03-29 09:28:49 +08:00
}
instance.state.logoHeight = 0;
instance.state.logoWidth = 0;
2022-09-27 15:02:35 +02:00
}
2026-03-29 09:28:49 +08:00
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
2026-03-29 09:28:49 +08:00
#endif
)
return false; // Paths
2026-03-29 09:28:49 +08:00
if (ffStrbufIgnCaseEqualS(name, "none")) {
logoPrintNone();
2022-09-27 15:02:35 +02:00
return true;
}
2026-01-08 15:56:44 +08:00
const FFlogo* logo = ffLogoGetBuiltinForName(name, size);
2026-07-10 18:08:04 +08:00
if (logo == nullptr) {
2022-06-08 10:59:33 +02:00
return false;
2026-03-29 09:28:49 +08:00
}
2022-06-08 10:59:33 +02:00
logoPrintStruct(logo);
2022-09-27 15:02:35 +02:00
2022-06-08 10:59:33 +02:00
return true;
}
2026-05-28 19:18:35 +08:00
void ffLogoPrintDetected(FFLogoSize size) {
2023-07-06 23:16:05 +08:00
logoPrintStruct(logoGetBuiltinDetected(size));
2022-06-08 10:59:33 +02:00
}
2026-03-29 09:28:49 +08:00
static bool logoPrintData(bool doColorReplacement, FFstrbuf* source) {
if (source->length == 0) {
2023-01-28 11:14:16 +01:00
return false;
2026-03-29 09:28:49 +08:00
}
2023-01-28 11:14:16 +01:00
logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);
2025-06-02 11:46:11 +08:00
ffLogoPrintChars(source->chars, doColorReplacement);
2023-01-28 11:14:16 +01:00
return true;
2022-12-12 13:43:10 +01:00
}
2026-03-29 09:28:49 +08:00
static bool updateLogoPath(void) {
2023-10-25 14:43:46 +08:00
FFOptionsLogo* options = &instance.config.logo;
2026-03-29 09:28:49 +08:00
if (ffPathExists(options->source.chars, FF_PATHTYPE_FILE)) {
return true;
2026-03-29 09:28:49 +08:00
}
2026-03-29 09:28:49 +08:00
if (ffStrbufEqualS(&options->source, "-")) { // stdin
return true;
2026-03-29 09:28:49 +08:00
}
2025-04-03 22:42:15 +08:00
#if !FF_MODULE_DISABLE_MEDIA
2026-03-29 09:28:49 +08:00
if (ffStrbufIgnCaseEqualS(&options->source, "media-cover")) {
const FFMediaResult* media = ffDetectMedia(true);
2026-03-29 09:28:49 +08:00
if (media->cover.length == 0) {
return false;
2026-03-29 09:28:49 +08:00
}
ffStrbufSet(&options->source, &media->cover);
return true;
}
#endif
FF_STRBUF_AUTO_DESTROY fullPath = ffStrbufCreateA(128);
2026-03-29 09:28:49 +08:00
if (ffPathExpandEnv(options->source.chars, &fullPath) && ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) {
ffStrbufDestroy(&options->source);
ffStrbufInitMove(&options->source, &fullPath);
return true;
}
2026-03-29 09:28:49 +08:00
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);
2026-03-29 09:28:49 +08:00
if (ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) {
ffStrbufDestroy(&options->source);
ffStrbufInitMove(&options->source, &fullPath);
return true;
}
}
return false;
}
2026-03-29 09:28:49 +08:00
static bool logoPrintFileIfExists(bool doColorReplacement, bool raw) {
2023-10-25 14:43:46 +08:00
FFOptionsLogo* options = &instance.config.logo;
FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
if (ffStrbufEqualS(&options->source, "-")
? !ffAppendFDBuffer(FFUnixFD2NativeFD(STDIN_FILENO), &content)
: !ffAppendFileBuffer(options->source.chars, &content)) {
if (instance.config.display.showErrors) {
2025-06-02 11:45:06 +08:00
fprintf(stderr, "Logo: Failed to load file content from logo source: %s\n", options->source.chars);
2026-03-29 09:28:49 +08:00
}
2022-06-08 10:59:33 +02:00
return false;
2022-04-02 14:35:43 +02:00
}
logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);
2026-03-29 09:28:49 +08:00
if (raw) {
return ffLogoPrintCharsRaw(content.chars, content.length, instance.config.display.showErrors);
2026-03-29 09:28:49 +08:00
}
ffLogoPrintChars(content.chars, doColorReplacement);
2022-06-08 10:59:33 +02:00
return true;
}
#if FF_HAVE_IMAGE_LOGO
2026-03-29 09:28:49 +08:00
static bool logoPrintImageIfExists(FFLogoType logo, bool printError) {
if (!ffLogoPrintImageIfExists(logo, printError)) {
2022-06-08 10:59:33 +02:00
return false;
2026-03-29 09:28:49 +08:00
}
2022-04-29 21:44:32 +02:00
logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);
2022-06-08 10:59:33 +02:00
return true;
2022-04-02 14:35:43 +02:00
}
#endif
2022-04-02 14:35:43 +02:00
2026-03-29 09:28:49 +08:00
static bool logoTryKnownType(void) {
2023-10-25 14:43:46 +08:00
FFOptionsLogo* options = &instance.config.logo;
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_NONE) {
2024-06-03 14:15:45 +08:00
logoPrintNone();
2023-01-28 11:14:16 +01:00
return true;
}
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_BUILTIN) {
return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN);
2026-03-29 09:28:49 +08:00
}
2023-07-06 23:16:05 +08:00
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_SMALL) {
2023-07-06 23:16:05 +08:00
return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_SMALL);
2026-03-29 09:28:49 +08:00
}
2023-01-28 11:14:16 +01:00
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_DATA) {
2025-06-02 11:46:11 +08:00
return logoPrintData(true, &options->source);
2026-03-29 09:28:49 +08:00
}
2023-01-28 11:14:16 +01:00
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_DATA_RAW) {
2025-06-02 11:46:11 +08:00
return logoPrintData(false, &options->source);
2026-03-29 09:28:49 +08:00
}
2025-06-02 11:46:11 +08:00
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_COMMAND_RAW) {
2025-06-02 11:46:11 +08:00
FF_STRBUF_AUTO_DESTROY source = ffStrbufCreate();
2026-05-22 16:47:55 +08:00
const char* error = ffProcessAppendStdOut(&source, (char* const[]){
2026-03-29 09:28:49 +08:00
#ifdef _WIN32
"cmd.exe", "/c",
#else
"/bin/sh", "-c",
#endif
options->source.chars,
2026-07-10 18:08:04 +08:00
nullptr });
2026-03-29 09:28:49 +08:00
if (error) {
if (instance.config.display.showErrors) {
2025-06-02 11:46:11 +08:00
fprintf(stderr, "Logo: failed to execute command `%s`: %s\n", options->source.chars, error);
2026-03-29 09:28:49 +08:00
}
2025-06-02 11:46:11 +08:00
return false;
}
return logoPrintData(false, &source);
}
2023-01-28 11:14:16 +01:00
2026-03-29 09:28:49 +08:00
// 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);
2026-03-29 09:28:49 +08:00
}
return false;
}
2023-01-28 11:14:16 +01:00
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_FILE) {
return logoPrintFileIfExists(true, false);
2026-03-29 09:28:49 +08:00
}
2023-01-28 11:14:16 +01:00
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_FILE_RAW) {
return logoPrintFileIfExists(false, false);
2026-03-29 09:28:49 +08:00
}
2023-01-28 11:14:16 +01:00
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_IMAGE_RAW) {
return logoPrintFileIfExists(false, true);
2026-03-29 09:28:49 +08:00
}
2022-06-08 10:59:33 +02:00
#if FF_HAVE_IMAGE_LOGO
return logoPrintImageIfExists(options->type, instance.config.display.showErrors);
#else
// No image logo type can be selected in this build (see ffOptionsParseLogoCommandLine), and
// FF_LOGO_TYPE_IMAGE_RAW was handled above, so nothing printable is left for this source.
if (instance.config.display.showErrors) {
fprintf(stderr, "Logo: Unsupported logo type for source: %s\n", options->source.chars);
}
return false;
#endif
2023-01-28 11:14:16 +01:00
}
2026-03-29 09:28:49 +08:00
void ffLogoPrint(void) {
2023-10-25 14:43:46 +08:00
const FFOptionsLogo* options = &instance.config.logo;
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_NONE) {
logoPrintNone();
return;
}
2026-03-29 09:28:49 +08:00
// If the source is not set, we can directly print the detected logo.
if (options->source.length == 0) {
2026-05-28 19:18:35 +08:00
ffLogoPrintDetected(options->type == FF_LOGO_TYPE_SMALL ? FF_LOGO_SIZE_SMALL : FF_LOGO_SIZE_NORMAL);
2022-06-08 10:59:33 +02:00
return;
}
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
// 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
2026-03-29 09:28:49 +08:00
if (options->type == FF_LOGO_TYPE_BUILTIN || options->type == FF_LOGO_TYPE_SMALL) {
2025-06-02 11:45:06 +08:00
fprintf(stderr, "Logo: Failed to load %s logo: %s\n", options->type == FF_LOGO_TYPE_BUILTIN ? "builtin" : "builtin small", options->source.chars);
2026-03-29 09:28:49 +08:00
}
}
2026-05-28 19:18:35 +08:00
ffLogoPrintDetected(FF_LOGO_SIZE_UNKNOWN);
}
2022-06-08 10:59:33 +02:00
return;
}
2022-04-02 14:35:43 +02:00
2026-03-29 09:28:49 +08:00
// If source matches the name of a builtin logo, print it and return.
if (logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN)) {
2022-06-08 10:59:33 +02:00
return;
2026-03-29 09:28:49 +08:00
}
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
// Make sure the logo path is set correctly.
if (updateLogoPath()) {
if (ffStrbufEndsWithIgnCaseS(&options->source, ".raw")) {
if (logoPrintFileIfExists(false, true)) {
2025-07-16 14:29:41 +08:00
return;
2026-03-29 09:28:49 +08:00
}
2025-07-16 14:29:41 +08:00
}
2023-01-28 11:14:16 +01:00
#if FF_HAVE_IMAGE_LOGO
// Try to load the logo as an image.
2026-03-29 09:28:49 +08:00
if (!ffStrbufEndsWithIgnCaseS(&options->source, ".txt")) {
#if !FF_MODULE_DISABLE_TERMINAL
2025-07-16 14:29:41 +08:00
const FFTerminalResult* terminal = ffDetectTerminal();
bool supportsIterm2 = ffStrbufEqualS(&terminal->prettyName, "iTerm");
if (supportsIterm2 && logoPrintImageIfExists(FF_LOGO_TYPE_IMAGE_ITERM, false)) {
return;
}
2026-03-29 09:28:49 +08:00
// Terminal emulators that support kitty graphics protocol.
2025-07-16 14:29:41 +08:00
bool supportsKitty =
ffStrbufIgnCaseEqualS(&terminal->processName, "kitty") ||
ffStrbufIgnCaseEqualS(&terminal->processName, "konsole") ||
ffStrbufIgnCaseEqualS(&terminal->processName, "wezterm") ||
ffStrbufIgnCaseEqualS(&terminal->processName, "wayst") ||
ffStrbufIgnCaseEqualS(&terminal->processName, "ghostty") ||
#ifdef __APPLE__
2025-07-16 14:29:41 +08:00
ffStrbufIgnCaseEqualS(&terminal->processName, "WarpTerminal") ||
#else
2025-07-16 14:29:41 +08:00
ffStrbufIgnCaseEqualS(&terminal->processName, "warp") ||
#endif
2025-07-16 14:29:41 +08:00
false;
#else
bool supportsKitty = false;
#endif
2025-07-16 14:29:41 +08:00
2026-03-29 09:28:49 +08:00
// 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)) {
2025-07-16 14:29:41 +08:00
return;
2026-03-29 09:28:49 +08:00
}
2025-07-16 14:29:41 +08:00
}
#endif
2025-07-16 14:29:41 +08:00
2026-03-29 09:28:49 +08:00
// Try to load the logo as a file. If it succeeds, print it and return.
if (logoPrintFileIfExists(true, false)) {
2025-07-13 20:15:48 +08:00
return;
2026-03-29 09:28:49 +08:00
}
} else {
if (instance.config.display.showErrors) {
2025-07-16 14:29:41 +08:00
fprintf(stderr, "Logo: Failed to resolve logo source: %s\n", options->source.chars);
2026-03-29 09:28:49 +08:00
}
2025-07-13 20:11:12 +08:00
}
2022-06-08 10:59:33 +02:00
2026-05-28 19:18:35 +08:00
ffLogoPrintDetected(FF_LOGO_SIZE_UNKNOWN);
2022-06-08 10:59:33 +02:00
}
2026-03-29 09:28:49 +08:00
void ffLogoPrintLine(void) {
2026-05-22 16:47:55 +08:00
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);
2026-05-22 16:47:55 +08:00
if (logo->position == FF_LOGO_POSITION_RIGHT) {
printf("\033[9999999C\033[%uD", cache->rightOffset);
ffStrbufWriteTo(&line->chars, stdout);
2026-05-22 16:47:55 +08:00
fputs("\033[G", stdout);
} else {
ffStrbufWriteTo(&line->chars, stdout);
2026-05-22 16:47:55 +08:00
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);
2026-05-22 16:47:55 +08:00
}
} else if (instance.state.logoWidth > 0) {
printf("\033[%uC", instance.state.logoWidth);
2026-03-29 09:28:49 +08:00
}
2022-06-06 14:02:21 +02:00
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
2026-03-29 09:28:49 +08:00
}
++instance.state.keysHeight;
2022-04-06 14:02:05 +02:00
}
2026-03-29 09:28:49 +08:00
void ffLogoPrintRemaining(void) {
2026-05-22 16:47:55 +08:00
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;
}
2026-03-29 09:28:49 +08:00
if (instance.state.keysHeight <= instance.state.logoHeight) {
2023-06-28 10:04:10 +08:00
ffPrintCharTimes('\n', instance.state.logoHeight - instance.state.keysHeight + 1);
2026-03-29 09:28:49 +08:00
}
2023-06-28 10:04:10 +08:00
instance.state.keysHeight = instance.state.logoHeight + 1;
2022-04-02 14:35:43 +02:00
}
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
void ffLogoBuiltinPrint(void) {
2023-10-25 14:43:46 +08:00
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();
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
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]);
2026-03-29 09:28:49 +08:00
} else {
ffStrbufSetF(&buf, "\e[%sm%s:\e[0m\n", logo->colors[0], logo->names[0]);
2026-03-29 09:28:49 +08:00
}
ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
logoPrintStruct(logo);
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
for (uint8_t i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) {
ffStrbufClear(&options->colors[i]);
2026-03-29 09:28:49 +08:00
}
}
2022-06-08 10:59:33 +02:00
}
}
2026-03-29 09:28:49 +08:00
void ffLogoBuiltinList(void) {
uint32_t counter = 0;
2026-03-29 09:28:49 +08:00
for (uint8_t ch = 0; ch < 26; ++ch) {
for (const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) {
++counter;
printf("%u)%s ", counter, counter < 10 ? " " : "");
2022-06-08 10:59:33 +02:00
2026-03-29 09:28:49 +08:00
for (
const char* const* names = logo->names;
2026-07-10 18:08:04 +08:00
*names != nullptr && names <= &logo->names[FASTFETCH_LOGO_MAX_NAMES];
2026-03-29 09:28:49 +08:00
++names) {
printf("\"%s\" ", *names);
2026-03-29 09:28:49 +08:00
}
2022-06-08 10:59:33 +02:00
putchar('\n');
}
2022-06-08 10:59:33 +02:00
}
}
2026-03-29 09:28:49 +08:00
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]);
2026-03-29 09:28:49 +08:00
}
}
2022-06-08 10:59:33 +02:00
}
2026-01-08 15:56:44 +08:00
2026-03-29 09:28:49 +08:00
const FFlogo* ffLogoGetBuiltinForName(const FFstrbuf* name, FFLogoSize size) {
2026-01-08 15:56:44 +08:00
return ffStrbufEqualS(name, "?") ? &ffLogoUnknown : logoGetBuiltin(name, size);
}
2026-03-29 09:28:49 +08:00
const FFlogo* ffLogoGetBuiltinDetected(FFLogoSize size) {
2026-01-08 15:56:44 +08:00
return logoGetBuiltinDetected(size);
}